1 Introduction

This R notebook implements the analysis of the Record-seq and RNA-seq readout of the transient diet experiments described in ‘Noninvasive assessment of gut function using transcriptional recording sentinel cells’ manuscript. The following files are stored in the data directory within the working directory:

transient-diet/
    secondaryAnalysis.Rmd
    data/
        transientDiet1_Recordseq_genomealigning.txt
        transientDiet1_Recordseq_metadata.txt
        transientDiet1_RNAseq_genomealigning.txt
        transientDiet1_RNAseq_metadata.txt
        transientDiet1_day14_RNAseq_genomealigning.txt
        transientDiet1_day14_RNAseq_metadata.txt  
        transientDiet2_Recordseq_genomealigning.txt
        transientDiet2_Recordseq_metadata.txt
        transientDiet2_RNAseq_genomealigning.txt
        transientDiet2_RNAseq_metadata.txt
        Chow.Fat.pathway.txt
        Chow.Starch.pathway.txt
        Fat.Starch.pathway.txt
    

2 Libraries

The recoRdseq package and dependencies are required for this analysis, and the fantastic patchwork package is used for visualization.

if(!require(devtools)){
  install.packages("devtools")
}
library(devtools)
if(!require(eulerr)){
  install.packages("eulerr")
}
library(eulerr)
if(!require(plyr)){
  install.packages("plyr")
}
library(plyr)
if(!require(recoRdseq)){
  install_github("plattlab/Transcriptional-Recording", subdir="recoRdseq")
}
if(!require(factoextra)){
  install.packages("factoextra")
}
library(factoextra)
if(!require(patchwork)){
  install.packages('patchwork')
}
if(!require(ggrepel)){
  install.packages('ggrepel')
}
library(recoRdseq)
library(patchwork)
library(stringr)
library(ggrepel)
colour_code = list(
  Diet = c(Chow = "#538bce", Fat="#ed915c", Starch='#42bb7f')) # we set a consistent color scheme for the three diet groups

theme_pub<-theme_minimal()+
  theme(legend.position="bottom", legend.justification="center", legend.margin=margin(0,0,0,0),legend.box.margin=margin(-10,-10,-10,-10),plot.title = element_text(hjust = 0.5), legend.spacing.y =  unit(0, 'mm'), legend.box='vertical', legend.key.size = unit(0.1, "cm"),legend.key.width = unit(0.1,"cm"), legend.text=element_text(size=5), text = element_text(size=5), panel.grid.minor = element_blank(), axis.text = element_text(size=5, colour='black'), panel.grid.major = element_line(size = 0.24, colour='gray1', linetype = 2)) # we set a consistent theme for ggplot objects

custom.config = umap.defaults
custom.config$random_state = 2

3 Transient Diet 1

Data for the transient diet experiment with 14 days is analyzed first.

3.1 Importing and pre-processing data for transient diet 1

We import the data matrices for both Record-seq and RNA-seq, filter them for lowly expressed genes as well as outlier samples with low cumulative counts, and use vst from DESeq2 to normalize and transform the data. We use a threshold of 10k counts for excluding Record-seq samples and 100k counts for excluding RNA-seq samples.

rec1<-as.data.frame(read.table("data/transientDiet1_Recordseq_genomealigning.txt", header = TRUE))
rec1d<-as.data.frame(read.table("data/transientDiet1_Recordseq_metadata.txt", header = TRUE))
DEList<-recoRdseq.preprocess(rec1, rec1d, minCountsPerSample = 10000)
rec1<-DEList[[1]]
rec1d<-DEList[[2]]
rec1d<-rec1d[rec1d$Day>1,]
rec1<-rec1[,rownames(rec1d)]
rec1_tf<-recoRdseq.transform(rec1, rec1d,transformation = 'vst')

rna1<-as.data.frame(read.table("data/transientDiet1_RNAseq_genomealigning.txt", header = TRUE))
rna1d<-as.data.frame(read.table("data/transientDiet1_RNAseq_metadata.txt", header = TRUE))
DEList<-recoRdseq.preprocess(rna1, rna1d, minCountsPerSample = 100000)
rna1<-DEList[[1]]
rna1d<-DEList[[2]]
rna1_tf<-recoRdseq.transform(rna1, rna1d)
rnadays<-unique(rna1d$Day)
rna1_day14<-as.data.frame(read.table("data/transientDiet1_day14_RNAseq_genomealigning.txt", header = TRUE))
rna1d_day14<-as.data.frame(read.table("data/transientDiet1_day14_RNAseq_metadata.txt", header = TRUE))
rna1d_day14<-rna1d_day14[,1:3]
DEList<-recoRdseq.preprocess(rna1_day14, rna1d_day14, minCountsPerSample = 100000)
rna1_day14<-DEList[[1]]
rna1d_day14<-DEList[[2]]
rna1_day14tf<-recoRdseq.transform(rna1_day14, rna1d_day14, transformation = 'vst')

3.2 Data exploration

We use Principal Component analysis and UMAP for dimensionality reduction and exploring clusters in an unsupervised fashion in our data. We first generate these for the entire dataset from Record-seq:

rec1sds <- rowSds(as.matrix(rec1_tf))
o <- order(rec1sds, decreasing = TRUE)
rec1PCA<-prcomp(t(rec1_tf[o[1:500],]))
pca_stat<-summary(rec1PCA)
pca_variance<-pca_stat$importance[2,]
rec1PCA<-as.data.frame(rec1PCA$x)
rec1PCA$Diet<-rec1d$Diet
rec1PCA$Day<-factor(rec1d$Day, levels = 1:20)
rec1PCAplot<-ggplot(rec1PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data - all days (transient diet 1)")


rec1UMAP<-umap(rec1PCA[,1:(ncol(rec1PCA)-2)], custom.config)
rec1UMAP<-as.data.frame(rec1UMAP$layout)
rec1UMAP$Day<-factor(rec1d$Day, levels = 2:14)
rec1UMAP$Diet<-rec1d$Diet
colnames(rec1UMAP)[1:2]<-c('UMAP1','UMAP2')
rec1UMAPplot<-ggplot(rec1UMAP, aes(x=UMAP1, y=UMAP2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.24)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+scale_fill_manual(values = as.vector(colour_code$Diet))+ggtitle("UMAP plot for Record-seq data - all days (transient diet 1)")

rec1PCAplot+rec1UMAPplot+plot_annotation(tag_levels = 'A')

For a comparison between Record-seq and RNA-seq, we check if the diet groups can be classified using PCA on day 7 (the last day when the mice are fed different diets, before switching all mice to a ‘Chow’ diet)


rec1d_day7<-rec1d[rec1d$Day==7,]
rec1_day7<-rec1[, rownames(rec1d_day7)]
rec1_day7tf<-recoRdseq.transform(rec1_day7, rec1d_day7)
rec1_day7_sds <- rowSds(as.matrix(rec1_day7tf))
o <- order(rec1_day7_sds, decreasing = TRUE)
rec1_day7PCA<-prcomp(t(rec1_day7tf[o[1:500],]))
pca_stat<-summary(rec1_day7PCA)
pca_variance<-pca_stat$importance[2,]
rec1_day7PCA<-as.data.frame(rec1_day7PCA$x)
rec1_day7PCA$Diet<-rec1d_day7$Diet
rec1_day7PCA$Day<-factor(rec1d_day7$Day, levels = c(7))
rec1_day7PCAplot<-ggplot(rec1_day7PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data on day 7 (transient diet 1)")

rna1_sds <- rowSds(as.matrix(rna1_tf))
o <- order(rna1_sds, decreasing = TRUE)
rna1_PCA<-prcomp(t(rna1_tf[o[1:500],]))
pca_stat<-summary(rna1_PCA)
pca_variance<-pca_stat$importance[2,]
rna1_PCA<-as.data.frame(rna1_PCA$x)
rna1_PCA$Diet<-rna1d$Diet
rna1_PCA$Day<-factor(rna1d$Day, levels = c(7))
rna1_day7PCAplot<-ggplot(rna1_PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of RNA-seq data on day 7 (transient diet 1) ")


rna1_day7PCAplot+rec1_day7PCAplot+plot_annotation(tag_levels = 'A')

Next, we check if Record-seq or RNA-seq can distinguish the samples on day 14 (7 days after switching all samples to Chow diet).


rec1d_day14<-rec1d[rec1d$Day==14,]
rec1_day14<-rec1[, rownames(rec1d_day14)]
rec1_day14tf<-recoRdseq.transform(rec1_day14, rec1d_day14)
rec1_day14_sds <- rowSds(as.matrix(rec1_day14tf))
o <- order(rec1_day14_sds, decreasing = TRUE)
rec1_day14PCA<-prcomp(t(rec1_day14tf[o[1:500],]))
pca_stat<-summary(rec1_day14PCA)
pca_variance<-pca_stat$importance[2,]
rec1_day14PCA<-as.data.frame(rec1_day14PCA$x)
rec1_day14PCA$Diet<-rec1d_day14$Diet
rec1_day14PCA$Day<-factor(rec1d_day14$Day, levels = c(14))
rec1_day14PCAplot<-ggplot(rec1_day14PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data on day 14 (transient diet 1)")

rna1_day14_sds <- rowSds(as.matrix(rna1_day14tf))
o <- order(rna1_day14_sds, decreasing = TRUE)
rna1_day14_PCA<-prcomp(t(rna1_day14tf[o[1:500],]))
pca_stat<-summary(rna1_day14_PCA)
pca_variance<-pca_stat$importance[2,]
rna1_day14_PCA<-as.data.frame(rna1_day14_PCA$x)
rna1_day14_PCA$Diet<-rna1d_day14$Diet
rna1_day14_PCA$Day<-factor(rna1d_day14$Day, levels = c(14))
rna1_day14PCAplot<-ggplot(rna1_day14_PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of RNA-seq data on day 14 (transient diet 1) ")


rna1_day14PCAplot+rec1_day14PCAplot+plot_annotation(tag_levels = 'A')

3.3 Discovery of differentially expressed genes (DEGs):

We identify DEGs for day 7 (since this is the final day when the mice are fed separate diets, and RNA-seq is also available for this day). We define DEGs as genes identified to be significantly differentially expressed using a threshold (padj < 0.05) by both DESeq2 and edgeR (multiple testing, since we have 3 groups).

rec1.deseq<-recoRdseq.DE(rec1_day7, rec1d_day7, tool='DESeq2')
rec1.edger<-recoRdseq.DE(rec1_day7, rec1d_day7, tool='edgeR')
rec1.deseq.genes<-recoRdseq.filterDEG(rec1.deseq, p = 0.05)
rec1.edger.genes<-recoRdseq.filterDEG(rec1.edger, p = 0.05)
rec1.DEG<-rec1.deseq[intersect(rec1.deseq.genes, rec1.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec1.deseq))))]
rec1.DEG$geneID<-as.character(rec1.DEG$geneID)
rec1.DEG<-rec1.DEG[order(rec1.DEG$padj),]
rec1.DEG$geneID<-as.character(rec1.DEG$geneID)
rna1.deseq<-recoRdseq.DE(rna1, rna1d, tool='DESeq2')
rna1.edger<-recoRdseq.DE(rna1, rna1d, tool='edgeR')
rna1.deseq.genes<-recoRdseq.filterDEG(rna1.deseq, p = 0.05)
rna1.edger.genes<-recoRdseq.filterDEG(rna1.edger, p = 0.05)
rna1.DEG<-rna1.deseq[intersect(rna1.deseq.genes, rna1.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rna1.deseq))))]
rna1.DEG$geneID<-as.character(rna1.DEG$geneID)

rec1.novel<-rec1.DEG[-which(rec1.DEG$geneID%in%rna1.DEG$geneID),]

For Record-seq, we also look for DE genes over days 2-7 in Record-seq using a looser confidence threshold (padj <0.1) to identify consistent diet-signature genes.

rec1.DEG.list<-list()
rec1.er<-list()
rec1.de<-list()
rec1.global.DEG<-c()
for(i in unique(rec1d$Day)){
  if(i<8){
    dt<-rec1d[which(rec1d$Day==i), 1, drop=FALSE]
    de<-rec1[,which(colnames(rec1)%in%rownames(dt))]
    rec1.de[[i]]<-recoRdseq.DE(de,dt,tool='DESeq2')
    rec1.er[[i]]<-recoRdseq.DE(de,dt,tool='edgeR')
    rec1.de.genes<-recoRdseq.filterDEG(rec1.de[[i]], p = 0.1)
    rec1.er.genes<-recoRdseq.filterDEG(rec1.er[[i]], p = 0.1)
    rec1.DEG.list[[i]]<-rec1.de[[i]][intersect(rec1.de.genes, rec1.er.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec1.de[[i]]))))]
    rec1.global.DEG<- c(rec1.global.DEG, as.character(intersect(rec1.de.genes,rec1.er.genes)))
  }
}
rec1.global.DEG1<-as.data.frame(table(rec1.global.DEG)[order(table(rec1.global.DEG), decreasing = TRUE)])
rec1.global.DEG2<-as.data.frame(table(rec1.global.DEG)[order(table(rec1.global.DEG), decreasing = TRUE)])
colnames(rec1.global.DEG1)<-c("geneID", "days_DE")
colnames(rec1.global.DEG2)<-c("geneID", "days_DE")
rec1.global.DEG1$geneID<-as.character(rec1.global.DEG1$geneID)
rec1.global.DEG2$geneID<-as.character(rec1.global.DEG2$geneID)
for(i in unique(rec1d$Day)){
  if(i<8){
  rec1.global.DEG1$V1<-rec1.de[[i]][rec1.global.DEG1$geneID,4]
  colnames(rec1.global.DEG1)[ncol(rec1.global.DEG1)]<-paste0("log2FoldChange_FC_day", i)
  rec1.global.DEG2$V1<-rec1.de[[i]][rec1.global.DEG2$geneID,8]
  colnames(rec1.global.DEG2)[ncol(rec1.global.DEG2)]<-paste0("log2FoldChange_SC_day", i)

  }
}
rec1.global.DEG1$log2FoldChange.max<-rec1.global.DEG1[,3:ncol(rec1.global.DEG1)][cbind(1:nrow(rec1.global.DEG1[,3:ncol(rec1.global.DEG1)]), max.col(replace(x <- abs(rec1.global.DEG1[,3:ncol(rec1.global.DEG1)]), is.na(x), -Inf)))]
rec1.global.DEG2$log2FoldChange.max<-rec1.global.DEG2[,3:ncol(rec1.global.DEG2)][cbind(1:nrow(rec1.global.DEG2[,3:ncol(rec1.global.DEG2)]), max.col(replace(x <- abs(rec1.global.DEG2[,3:ncol(rec1.global.DEG2)]), is.na(x), -Inf)))]

rec1.global.DEG<-rec1.global.DEG1[,c(1,2,ncol(rec1.global.DEG1))]
rec1.global.DEG$V1<-rec1.global.DEG2[,ncol(rec1.global.DEG1)]
colnames(rec1.global.DEG)[3]<-"log2FoldChange.max_FC"
colnames(rec1.global.DEG)[4]<-"log2FoldChange.max_SC"

3.4 Plotting individual DEGs:

We plot vst-transformed genome-aligning spacer counts for 6 genes in the gntR pathway for chow and starch fed mice on day 7.

gntR_genes<-c('eda','edd', 'gntT', 'kdgT', 'gntU', 'gntK')
de<-rec1d_day7[rec1d_day7$Diet!='Fat',]
dt<-rec1_day7tf[gntR_genes, rownames(de)]
rec1.gntR.plot.df<-data.frame(Diet=de$Diet, t(dt))
rec1.gntR.plot.df<-melt(rec1.gntR.plot.df, id.vars = 'Diet')
rec1.gntR.plot<-ggplot(rec1.gntR.plot.df, aes(y=value, x=variable, fill=Diet, color='black'))+geom_boxplot(size=0.24, outlier.size=0)+geom_point(size=0.48, position = position_dodge(0.75))+theme_pub+ylab("gene-aligning spacer counts (vst-transformed)")+xlab("gene")+ggtitle("Record-seq counts on day 7 for gntR gene")+scale_fill_manual(values = as.vector(colour_code$Diet[c(1,3)]))+scale_color_manual(values = c("black"), guide='none')
rec1.gntR.plot+plot_annotation()

NA
NA

3.5 Heatmap for Record-seq and RNA-seq DEGs on day 7

We plot heatmaps showing hierarchical clustering of samples using detected DE genes for both Record-seq and RNA-seq on day 7.

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
ribosomal<-c(grep("rrs", rownames(rec1.DEG)), grep("rrl", rownames(rec1.DEG)))
if(length(ribosomal)>0){
  rec1.DEG<-rec1.DEG[-ribosomal,]
}
dheatmap<-as.data.frame(t(apply(rec1_day7tf[rec1.DEG$geneID,], 1, zscorestandardize)))
heatmap.rec1.day7<-pheatmap(dheatmap, annotation_col = rec1d_day7[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0, clustering_distance_cols = "canberra", treeheight_col = 5,show_colnames = FALSE, show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='Record-seq DEGs day 7')

ribosomal<-c(grep("rrs", rownames(rna1.DEG)), grep("rrl", rownames(rna1.DEG)))
if(length(ribosomal)>0){
  rna1.DEG<-rna1.DEG[-ribosomal,]
}
cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rna1_tf[rna1.DEG$geneID,], 1, zscorestandardize)))
heatmap.rna1.day7<-pheatmap(dheatmap, annotation_col = rna1d[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0, treeheight_col = 5, show_colnames = FALSE,show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='RNA-seq DEGs day 7')

3.6 Volcano plots for Record-seq DEGs

We perform pairwise DE analysis using DESeq2 and edgeR to identify log2FC and p-adj values for each diet pair on day 7, and plot volcanoes (log2FC>1.5, padj<0.1)

levels<-sort(unique(rec1d_day7[,1]))
pairwise.combo<-combn(levels, 2)
color.combo<-combn(colour_code$Diet, 2)
rec1.de.vals<-list()
rec1.ed.vals<-list()
vol.plots<-list()
DEG<-list()
for(i in 1:dim(pairwise.combo)[2]){
  ds<-rec1d_day7[which(rec1d_day7[,1]%in%pairwise.combo[,i]),]
  ds$Diet<-as.character(ds$Diet)
  dt<-rec1_day7[,rownames(ds)]
  dtf<-recoRdseq.transform(dt,ds)
  rec1.de.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'DESeq2')
  rec1.ed.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'edgeR')
  rownames(rec1.de.vals[[i]]) <- rec1.de.vals[[i]]$geneID
  rownames(rec1.ed.vals[[i]]) <- rec1.ed.vals[[i]]$geneID
  deseq.genes<-recoRdseq.filterDEG(rec1.de.vals[[i]], p = 0.1)
  edger.genes<-recoRdseq.filterDEG(rec1.ed.vals[[i]], p = 0.1)
  DEG[[i]]<-data.frame(row.names=intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes),log2Foldchange=rec1.de.vals[[i]][intersect(deseq.genes, edger.genes), 4], padj=rec1.de.vals[[i]][intersect(deseq.genes, edger.genes), 7])
  ribosomal<-c(grep("rrs", rownames(DEG[[i]])), grep("rrl", rownames(DEG[[i]])))
  if(length(ribosomal)>0){
      DEG[[i]]<-DEG[[i]][-ribosomal,]
  }
  DEG[[i]]$geneID<-as.character(DEG[[i]]$geneID)
  rec1.de.vals[[i]]<-rec1.de.vals[[i]][complete.cases(rec1.de.vals[[i]]),]
  rec1.de.vals[[i]]$Group<-'None'
  rec1.de.vals[[i]]$Group[ which(rec1.de.vals[[i]]$log2FoldChange>1.5&rec1.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[2]))
  rec1.de.vals[[i]]$Group[ which(rec1.de.vals[[i]]$log2FoldChange<(-1.5)&rec1.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", sort(unique(ds$Diet))[1])
 rec1.de.vals[[i]]$Group<-factor(rec1.de.vals[[i]]$Group, levels = c(paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[1])), paste0("upregulated.in.", sort(unique(ds$Diet))[2]), 'None' ))
  rec1.de.vals[[i]]$label<-FALSE
  m1<-rec1.de.vals[[i]][rec1.de.vals[[i]]$log2FoldChange>1.5, 'geneID'][1:10]
  m2<-rec1.de.vals[[i]][rec1.de.vals[[i]]$log2FoldChange<(-1.5), 'geneID'][1:10]
  m<-which(rec1.de.vals[[i]]$geneID%in%union(m1,m2))
  for(j in m){
    if(abs(rec1.de.vals[[i]]$log2FoldChange[j])>1.5&rec1.de.vals[[i]]$padj[j]<0.1){
      rec1.de.vals[[i]]$label[j]<-TRUE
    }
  }
  vol.plots[[i]]<-ggplot(rec1.de.vals[[i]], aes( x=log2FoldChange, y=(-log10(padj)), color=Group))+scale_colour_manual(values = c(color.combo[,i], 'gray70'))+geom_point(size=0.24)+geom_text_repel(data = rec1.de.vals[[i]][which(rec1.de.vals[[i]]$label),], aes( x=log2FoldChange, y=(-log10(padj)), label=geneID), size=1.76, show.legend=FALSE)+theme_pub+geom_vline(xintercept = 1.5, size=0.24)+geom_vline(xintercept = -1.5, size=0.24)+geom_hline(yintercept = 1, size=0.24)+xlab("log2 fold change")+ylab("-log10 p-adjusted value")+guides(color = guide_legend(override.aes = list(size=1.5)))
}

vol.plots[[1]] + vol.plots[[2]] +vol.plots[[3]] + plot_annotation(tag_levels = 'A')+plot_layout(ncol = 2)

3.7 Clustering on final day of experiment

We want to check whether information about diet groups prior to switch can be retrieved at day 14 - i.e 7 days after the switch. For this, we use diet-signature genes identified before the switch to hierarchically cluster the groups. Diet-signature genes are defined here as the top 10 genes by number of days (Record-seq) or p-adj value (RNA-seq) detected as enriched (log2FC > 2.5) prior to the switch. We can perfectly classify groups using Record-seq data, while for RNA-seq, the groups converge.

ribosomal<-c(grep("rrs", rec1.global.DEG$geneID), grep("rrl", rec1.global.DEG$geneID))
if(length(ribosomal)>0){
  rec1.global.DEG<-rec1.global.DEG[-ribosomal,]
}
geneShortList<-unique(c(rec1.global.DEG[which(rec1.global.DEG$log2FoldChange.max_FC>2.5), 1][1:10], rec1.global.DEG[which(rec1.global.DEG$log2FoldChange.max_SC>2.5), 1][1:10],rec1.global.DEG[which(rowMeans(rec1.global.DEG[,3:4])<(-2.5)), 1][1:10]))
cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rec1_day14tf[geneShortList,], 1, zscorestandardize)))
heatmap.rec1<-pheatmap(dheatmap, annotation_col = rec1d_day14[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0, treeheight_col=5, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE, color = cols,fontsize_number=5, main='Hierarchical clustering of Record-seq data on day 14 based on DEGS detected on day 7')

heatmap.genelist<-rec1_day14tf[geneShortList,]
colnames(heatmap.genelist)<-rec1d_day14$Diet


cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
geneShortList<-unique(c(rna1.DEG[which(rna1.DEG$log2FoldChange.Fat_vs_Chow>2.5), 1][1:12], rna1.DEG[which(rna1.DEG$log2FoldChange.Starch_vs_Chow>2.5), 1][1:12],rna1.DEG[which(rowMeans(rna1.DEG[,3:4])<(-2.5)), 1][1:12]))
dheatmap<-as.data.frame(t(apply(rna1_day14tf[geneShortList,], 1, zscorestandardize)))
dheatmap<-dheatmap[complete.cases(dheatmap),]
heatmap.rna1<-pheatmap(dheatmap, annotation_col = rna1d_day14[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0,  treeheight_col=5, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE, color = cols,fontsize_number=5, main='Hierarchical clustering of RNA-seq data on day 14 based on DEGS detected on day 7')

4 Transient Diet 2

We now analyze data for the extended transient diet experiment with 20 days.

4.1 Importing and pre-processing data for transient diet 2

We import the data matrices, filter them for lowly expressed genes as well as outlier samples with low cumulative counts, and use vst from DESeq2 to normalize and transform the data. We also exclude day1 from the analysis since we have emperically observed that the data are noisy for the first day of colonization. We use the same thresholds as the previous experiment for consistency.

rec2<-as.data.frame(read.table("data/transientDiet2_Recordseq_genomealigning.txt", header = TRUE))
rec2d<-as.data.frame(read.table("data/transientDiet2_Recordseq_metadata.txt", header = TRUE))
DEList<-recoRdseq.preprocess(rec2, rec2d, minCountsPerSample = 10000)
rec2<-DEList[[1]]
rec2d<-DEList[[2]]
rec2d<-rec2d[rec2d$Day>1,]
rec2<-rec2[,rownames(rec2d)]
rec2_tf<-recoRdseq.transform(rec2, rec2d)
rna2<-as.data.frame(read.table("data/transientDiet2_RNAseq_genomealigning.txt", header = TRUE))
rna2d<-as.data.frame(read.table("data/transientDiet2_RNAseq_metadata.txt", header = TRUE))
rna2d<-rna2d[,1:3]
DEList<-recoRdseq.preprocess(rna2, rna2d, minCountsPerSample = 100000)
rna2<-DEList[[1]]
rna2d<-DEList[[2]]
rna2_tf<-recoRdseq.transform(rna2, rna2d)
rnadays<-unique(rna2d$Day)

4.2 Data exploration

We use Principal Component analysis and UMAP for dimensionality reduction and exploring clusters in an unsupervised fashion in our data. We first generate these for the entire dataset from Record-seq:

rec2sds <- rowSds(as.matrix(rec2_tf))
o <- order(rec2sds, decreasing = TRUE)
rec2PCA<-prcomp(t(rec2_tf[o[1:500],]))
pca_stat<-summary(rec2PCA)
rec2.pca_variance<-pca_stat$importance[2,]
rec2PCA<-as.data.frame(rec2PCA$x)
rec2PCA$Diet<-rec2d$Diet
rec2PCA$Day<-factor(rec2d$Day, levels = 1:20)
rec2PCAplot<-ggplot(rec2PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(rec2.pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(rec2.pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data")

rec2UMAP<-umap(rec2PCA[,1:(ncol(rec2PCA)-2)], custom.config)
rec2UMAP<-as.data.frame(rec2UMAP$layout)
rec2UMAP$Day<-factor(rec2d$Day, levels = 1:20)
rec2UMAP$Diet<-rec2d$Diet
colnames(rec2UMAP)[1:2]<-c('UMAP1','UMAP2')
rec2UMAPplot<-ggplot(rec2UMAP, aes(x=UMAP1, y=UMAP2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+scale_fill_manual(values = as.vector(colour_code$Diet))+ggtitle("UMAP plot for Record-seq data (all days)")

rec2PCAplot+rec2UMAPplot+plot_annotation(tag_levels = 'A')

For a comparasion between Record-seq and RNA-seq, we exclude the days in Record-seq that don’t have corresponding RNA-seq data. We first check if the diet groups can be classified using PCA on day 7 (the last day when the mice are fed different diets, before switching all mice to a ‘Chow’ diet)


rec2d_day7<-rec2d[rec2d$Day==7,]
rec2_day7<-rec2[, rownames(rec2d_day7)]
rec2_day7tf<-recoRdseq.transform(rec2_day7, rec2d_day7)
rec2_day7_sds <- rowSds(as.matrix(rec2_day7tf))
o <- order(rec2_day7_sds, decreasing = TRUE)
rec2_day7PCA<-prcomp(t(rec2_day7tf[o[1:500],]))
pca_stat<-summary(rec2_day7PCA)
pca_variance<-pca_stat$importance[2,]
rec2_day7PCA<-as.data.frame(rec2_day7PCA$x)
rec2_day7PCA$Diet<-rec2d_day7$Diet
rec2_day7PCA$Day<-factor(rec2d_day7$Day, levels = c(7))
rec2_day7PCAplot<-ggplot(rec2_day7PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data on day 7 ")

rna2d_day7<-rna2d[rna2d$Day==7,]
rna2_day7<-rna2[, rownames(rna2d_day7)]
rna2_day7tf<-recoRdseq.transform(rna2_day7, rna2d_day7)
rna2_day7_sds <- rowSds(as.matrix(rna2_day7tf))
o <- order(rna2_day7_sds, decreasing = TRUE)
rna2_day7PCA<-prcomp(t(rna2_day7tf[o[1:500],]))
pca_stat<-summary(rna2_day7PCA)
pca_variance<-pca_stat$importance[2,]
rna2_day7PCA<-as.data.frame(rna2_day7PCA$x)
rna2_day7PCA$Diet<-rna2d_day7$Diet
rna2_day7PCA$Day<-factor(rna2d_day7$Day, levels = c(7))
rna2_day7PCAplot<-ggplot(rna2_day7PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of RNA-seq data on day 7 ")


rec2_day7PCAplot+rna2_day7PCAplot+plot_annotation(tag_levels = 'A')

We then compare the temporal trajectories of Record-seq and RNA-seq using UMAPs. Record-seq data retains information about prior diet groups till the final day, and clusters strongly based on group; whereas RNA-seq data has a pronounced temporal change, and initial clusters for different diet groups quickly converge.

rec2d_rna<-rec2d[which(rec2d$Day%in%rnadays),]
rec2_rna<-rec2[, rownames(rec2d_rna)]
rec2_rnatf<-recoRdseq.transform(rec2_rna, rec2d_rna)
rec2rnasds <- rowSds(as.matrix(rec2_rnatf))
o <- order(rec2rnasds, decreasing = TRUE)
rec2rnaPCA<-prcomp(t(rec2_rnatf[o[1:500],]))
pca_stat<-summary(rec2rnaPCA)
rec2rna.pca_variance<-pca_stat$importance[2,]
rec2rnaPCA<-as.data.frame(rec2rnaPCA$x)
rec2rnaPCA$Diet<-rec2d_rna$Diet
rec2rnaPCA$Day<-factor(rec2d_rna$Day, levels = rnadays)
rec2rnaUMAP<-umap(rec2rnaPCA[,1:(ncol(rec2rnaPCA)-2)], custom.config)
rec2rnaUMAP<-as.data.frame(rec2rnaUMAP$layout)
rec2rnaUMAP$Day<-factor(rec2d_rna$Day, levels = rnadays)
rec2rnaUMAP$Diet<-rec2d_rna$Diet
colnames(rec2rnaUMAP)[1:2]<-c('UMAP1','UMAP2')
rec2rnaUMAPplot<-ggplot(rec2rnaUMAP, aes(x=UMAP1, y=UMAP2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+scale_fill_manual(values = as.vector(colour_code$Diet))+ggtitle("UMAP plot for Record-seq data")

rna2sds <- rowSds(as.matrix(rna2_tf))
o <- order(rna2sds, decreasing = TRUE)
rna2PCA<-prcomp(t(rna2_tf[o[1:500],]))
pca_stat<-summary(rna2PCA)
rna2.pca_variance<-pca_stat$importance[2,]
rna2PCA<-as.data.frame(rna2PCA$x)
rna2PCA$Diet<-rna2d$Diet
rna2PCA$Day<-factor(rna2d$Day, levels = rnadays)
rna2UMAP<-umap(rna2PCA[,1:(ncol(rna2PCA)-2)], custom.config)
rna2UMAP<-as.data.frame(rna2UMAP$layout)
rna2UMAP$Day<-factor(rna2d$Day, levels = rnadays)
rna2UMAP$Diet<-rna2d$Diet
colnames(rna2UMAP)[1:2]<-c('UMAP1','UMAP2')
rna2UMAPplot<-ggplot(rna2UMAP, aes(x=UMAP1, y=UMAP2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+scale_fill_manual(values = as.vector(colour_code$Diet))+ggtitle("UMAP plot for RNA-seq data")
rec2rnaUMAPplot+rna2UMAPplot+plot_annotation(tag_levels = 'A')

4.3 Discovery of differentially expressed genes (DEGs):

We identify DEGs for day 7 (since this is the final day when the mice are fed separate diets, and RNA-seq is also available for this day). We define DEGs as genes identified to be significantly differentially expressed using a threshold (padj < 0.05) by both DESeq2 and edgeR (multiple testing, since we have 3 groups).

rec2.deseq<-recoRdseq.DE(rec2_day7, rec2d_day7, tool='DESeq2')
rec2.edger<-recoRdseq.DE(rec2_day7, rec2d_day7, tool='edgeR')
rec2.deseq.genes<-recoRdseq.filterDEG(rec2.deseq, p = 0.05)
rec2.edger.genes<-recoRdseq.filterDEG(rec2.edger, p = 0.05)
rec2.DEG<-rec2.deseq[intersect(rec2.deseq.genes, rec2.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec2.deseq))))]
rec2.DEG$geneID<-as.character(rec2.DEG$geneID)
rec2.DEG<-rec2.DEG[order(rec2.DEG$padj),]
ribosomal<-c(grep("rrs", rownames(rec2.DEG)), grep("rrl", rownames(rec2.DEG)))
if(length(ribosomal)>0){
  rec2.DEG<-rec2.DEG[-ribosomal,]
}
rna2.deseq<-recoRdseq.DE(rna2_day7, rna2d_day7, tool='DESeq2')
rna2.edger<-recoRdseq.DE(rna2_day7, rna2d_day7, tool='edgeR')
rna2.deseq.genes<-recoRdseq.filterDEG(rna2.deseq, p = 0.05)
rna2.edger.genes<-recoRdseq.filterDEG(rna2.edger, p = 0.05)
rna2.DEG<-rna2.deseq[intersect(rna2.deseq.genes, rna2.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rna2.deseq))))]
rna2.DEG$geneID<-as.character(rna2.DEG$geneID)
rna2.DEG<-rna2.DEG[order(rna2.DEG$padj),]
ribosomal<-c(grep("rrs", rownames(rna2.DEG)), grep("rrl", rownames(rna2.DEG)))
if(length(ribosomal)>0){
  rna2.DEG<-rna2.DEG[-ribosomal,]
}
rec2.novel<-rec2.DEG[-which(rec2.DEG$geneID%in%rna2.DEG$geneID),]

We also look for DE genes over days 2-7 in Record-seq using a looser confidence threshold (padj <0.1) to identify consistent diet-signature genes.

rec2.DEG.list<-list()
rec2.er<-list()
rec2.de<-list()
rec2.global.DEG<-c()
for(i in unique(rec2d$Day)){
  if(i<8){
    dt<-rec2d[which(rec2d$Day==i), 1, drop=FALSE]
    dt$Diet<-factor(dt$Diet)
    de<-rec2[,which(colnames(rec2)%in%rownames(dt))]
    rec2.de[[i]]<-recoRdseq.DE(de,dt,tool='DESeq2')
    rec2.er[[i]]<-recoRdseq.DE(de,dt,tool='edgeR')
    rec2.de.genes<-recoRdseq.filterDEG(rec2.de[[i]], p = 0.1)
    rec2.er.genes<-recoRdseq.filterDEG(rec2.er[[i]], p = 0.1)
    rec2.DEG.list[[i]]<-rec2.de[[i]][intersect(rec2.de.genes, rec2.er.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec2.de[[i]]))))]
    rec2.global.DEG<- c(rec2.global.DEG, as.character(intersect(rec2.de.genes,rec2.er.genes)))
  }
}
rec2.global.DEG1<-as.data.frame(table(rec2.global.DEG)[order(table(rec2.global.DEG), decreasing = TRUE)])
rec2.global.DEG2<-as.data.frame(table(rec2.global.DEG)[order(table(rec2.global.DEG), decreasing = TRUE)])
colnames(rec2.global.DEG1)<-c("geneID", "days_DE")
colnames(rec2.global.DEG2)<-c("geneID", "days_DE")
rec2.global.DEG1$geneID<-as.character(rec2.global.DEG1$geneID)
rec2.global.DEG2$geneID<-as.character(rec2.global.DEG2$geneID)
for(i in unique(rec2d$Day)){
  if(i<8){
  rec2.global.DEG1$V1<-rec2.de[[i]][rec2.global.DEG1$geneID,4]
  colnames(rec2.global.DEG1)[ncol(rec2.global.DEG1)]<-paste0("log2FoldChange_FC_day", i)
  rec2.global.DEG2$V1<-rec2.de[[i]][rec2.global.DEG2$geneID,8]
  colnames(rec2.global.DEG2)[ncol(rec2.global.DEG2)]<-paste0("log2FoldChange_SC_day", i)

  }
}
rec2.global.DEG1$log2FoldChange.max<-rec2.global.DEG1[,3:ncol(rec2.global.DEG1)][cbind(1:nrow(rec2.global.DEG1[,3:ncol(rec2.global.DEG1)]), max.col(replace(x <- abs(rec2.global.DEG1[,3:ncol(rec2.global.DEG1)]), is.na(x), -Inf)))]
rec2.global.DEG2$log2FoldChange.max<-rec2.global.DEG2[,3:ncol(rec2.global.DEG2)][cbind(1:nrow(rec2.global.DEG2[,3:ncol(rec2.global.DEG2)]), max.col(replace(x <- abs(rec2.global.DEG2[,3:ncol(rec2.global.DEG2)]), is.na(x), -Inf)))]

rec2.global.DEG<-rec2.global.DEG1[,c(1,2,ncol(rec2.global.DEG1))]
rec2.global.DEG$V1<-rec2.global.DEG2[,ncol(rec2.global.DEG2)]
colnames(rec2.global.DEG)[3]<-"log2FoldChange.max_FC"
colnames(rec2.global.DEG)[4]<-"log2FoldChange.max_SC"

4.4 Plotting individual DEGs:

We plot vst-transformed genome-aligning spacer counts for 6 genes in the gntR pathway for chow and starch fed mice on day 7.

gntR_genes<-c('eda','edd', 'gntT', 'kdgT', 'gntU', 'gntK')
de<-rec2d_day7[rec2d_day7$Diet!='Fat',]
dt<-rec2_day7tf[gntR_genes, rownames(de)]
rec2.gntR.plot.df<-data.frame(Diet=de$Diet, t(dt))
rec2.gntR.plot.df<-melt(rec2.gntR.plot.df, id.vars = 'Diet')
rec2.gntR.plot<-ggplot(rec2.gntR.plot.df, aes(y=value, x=variable, fill=Diet, color='black'))+geom_boxplot(size=0.24, outlier.size=0)+geom_point(size=0.48, position = position_dodge(0.75))+theme_pub+ylab("gene-aligning spacer counts (vst-transformed)")+xlab("gene")+ggtitle("Record-seq counts on day 7 for gntR gene")+scale_fill_manual(values = as.vector(colour_code$Diet[c(1,3)]))+scale_color_manual(values = c("black"), guide='none')
rec2.gntR.plot+plot_annotation()

4.5 Heatmap for Record-seq and RNA-seq DEGs on day 7

We plot heatmaps showing hierarchical clustering of samples using detected DE genes for both Record-seq and RNA-seq on day 7.

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rec2_day7tf[rec2.DEG$geneID,], 1, zscorestandardize)))
heatmap.rec2.day7<-pheatmap(dheatmap, annotation_col = rec2d_day7[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0,  treeheight_col = 5, show_colnames = FALSE, show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='Record-seq DEGs day 7')

heatmap.genelist<-heatmap.rec2.day7$tree_row$labels[heatmap.rec2.day7$tree_row$order]
heatmap.genelist<-rec2_day7tf[heatmap.genelist,]
colnames(heatmap.genelist)<-as.character(rec2d_day7$Diet)

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rna2_day7tf[rna2.DEG$geneID,], 1, zscorestandardize)))
heatmap.rna2.day7<-pheatmap(dheatmap, annotation_col = rna2d_day7[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0, treeheight_col = 5, show_colnames = FALSE,show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='RNA-seq DEGs day 7')

4.6 Volcano plots and heatmaps for Record-seq and RNA-seq DEGs:

We perform pairwise DE analysis using DESeq2 to identify log2FC and p-adj values for each diet pair on day 7, and plot volcanoes (log2FC>1.5, padj<0.1)

Record-seq:

levels<-sort(unique(rec2d_day7[,1]))
pairwise.combo<-combn(levels, 2)
color.combo<-combn(colour_code$Diet, 2)
rec2.de.vals<-list()
rec2.ed.vals<-list()
vol.plots<-list()
DEG<-list()
for(i in 1:dim(pairwise.combo)[2]){
  ds<-rec2d_day7[which(rec2d_day7[,1]%in%pairwise.combo[,i]),]
  ds$Diet<-as.character(ds$Diet)
  dt<-rec2_day7[,rownames(ds)]
  dtf<-recoRdseq.transform(dt,ds)
  rec2.de.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'DESeq2')
  rec2.ed.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'edgeR')
  rownames(rec2.de.vals[[i]]) <- rec2.de.vals[[i]]$geneID
  rownames(rec2.ed.vals[[i]]) <- rec2.ed.vals[[i]]$geneID
  deseq.genes<-recoRdseq.filterDEG(rec2.de.vals[[i]], p = 0.1)
  edger.genes<-recoRdseq.filterDEG(rec2.ed.vals[[i]], p = 0.1)
  DEG[[i]]<-data.frame(row.names=intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes),log2Foldchange=rec2.de.vals[[i]][intersect(deseq.genes, edger.genes), 4], padj=rec2.de.vals[[i]][intersect(deseq.genes, edger.genes), 7])
  ribosomal<-c(grep("rrs", rownames(DEG[[i]])), grep("rrl", rownames(DEG[[i]])))
  if(length(ribosomal)>0){
      DEG[[i]]<-DEG[[i]][-ribosomal,]
  }
  DEG[[i]]$geneID<-as.character(DEG[[i]]$geneID)
  rec2.de.vals[[i]]<-rec2.de.vals[[i]][complete.cases(rec2.de.vals[[i]]),]
  rec2.de.vals[[i]]$Group<-'None'
  rec2.de.vals[[i]]$Group[ which(rec2.de.vals[[i]]$log2FoldChange>1.5&rec2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[2]))
  rec2.de.vals[[i]]$Group[ which(rec2.de.vals[[i]]$log2FoldChange<(-1.5)&rec2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", sort(unique(ds$Diet))[1])
 rec2.de.vals[[i]]$Group<-factor(rec2.de.vals[[i]]$Group, levels = c(paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[1])), paste0("upregulated.in.", sort(unique(ds$Diet))[2]), 'None' ))
  rec2.de.vals[[i]]$label<-FALSE
  m1<-rec2.de.vals[[i]][rec2.de.vals[[i]]$log2FoldChange>1.5, 'geneID'][1:10]
  m2<-rec2.de.vals[[i]][rec2.de.vals[[i]]$log2FoldChange<(-1.5), 'geneID'][1:10]
  m<-which(rec2.de.vals[[i]]$geneID%in%union(m1,m2))
  for(j in m){
    if(abs(rec2.de.vals[[i]]$log2FoldChange[j])>1.5&rec2.de.vals[[i]]$padj[j]<0.1){
      rec2.de.vals[[i]]$label[j]<-TRUE
    }
  }
  vol.plots[[i]]<-ggplot(rec2.de.vals[[i]], aes( x=log2FoldChange, y=(-log10(padj)), color=Group))+scale_colour_manual(values = c(color.combo[,i], 'gray70'))+geom_point(size=0.24)+geom_text_repel(data = rec2.de.vals[[i]][which(rec2.de.vals[[i]]$label),], aes( x=log2FoldChange, y=(-log10(padj)), label=geneID), size=1.76, show.legend=FALSE)+theme_pub+geom_vline(xintercept = 1.5, size=0.24)+geom_vline(xintercept = -1.5, size=0.24)+geom_hline(yintercept = 1, size=0.24)+xlab("log2 fold change")+ylab("-log10 p-adjusted value")+guides(color = guide_legend(override.aes = list(size=1.5)))
}

vol.plots[[1]] + vol.plots[[2]] +vol.plots[[3]] + plot_annotation(tag_levels = 'A')+plot_layout(ncol = 2)

RNA-seq:

levels<-sort(unique(rna2d_day7[,1]))
pairwise.combo<-combn(levels, 2)
color.combo<-combn(colour_code$Diet, 2)
rna2.de.vals<-list()
rna2.ed.vals<-list()
vol.plots<-list()
DEG<-list()
for(i in 1:dim(pairwise.combo)[2]){
  ds<-rna2d_day7[which(rna2d_day7[,1]%in%pairwise.combo[,i]),]
  ds$Diet<-as.character(ds$Diet)
  dt<-rna2_day7[,rownames(ds)]
  dtf<-recoRdseq.transform(dt,ds)
  rna2.de.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'DESeq2')
  rna2.ed.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'edgeR')
  rownames(rna2.de.vals[[i]]) <- rna2.de.vals[[i]]$geneID
  rownames(rna2.ed.vals[[i]]) <- rna2.ed.vals[[i]]$geneID
  deseq.genes<-recoRdseq.filterDEG(rna2.de.vals[[i]], p = 0.1)
  edger.genes<-recoRdseq.filterDEG(rna2.ed.vals[[i]], p = 0.1)
  DEG[[i]]<-data.frame(row.names=intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes),log2Foldchange=rna2.de.vals[[i]][intersect(deseq.genes, edger.genes), 4], padj=rna2.de.vals[[i]][intersect(deseq.genes, edger.genes), 7])
  ribosomal<-c(grep("rrs", rownames(DEG[[i]])), grep("rrl", rownames(DEG[[i]])))
  if(length(ribosomal)>0){
      DEG[[i]]<-DEG[[i]][-ribosomal,]
  }
  DEG[[i]]$geneID<-as.character(DEG[[i]]$geneID)
  rna2.de.vals[[i]]<-rna2.de.vals[[i]][complete.cases(rna2.de.vals[[i]]),]
  rna2.de.vals[[i]]$Group<-'None'
  rna2.de.vals[[i]]$Group[ which(rna2.de.vals[[i]]$log2FoldChange>1.5&rna2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[2]))
  rna2.de.vals[[i]]$Group[ which(rna2.de.vals[[i]]$log2FoldChange<(-1.5)&rna2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", sort(unique(ds$Diet))[1])
 rna2.de.vals[[i]]$Group<-factor(rna2.de.vals[[i]]$Group, levels = c(paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[1])), paste0("upregulated.in.", sort(unique(ds$Diet))[2]), 'None' ))
  rna2.de.vals[[i]]$label<-FALSE
  m1<-rna2.de.vals[[i]][rna2.de.vals[[i]]$log2FoldChange>1.5, 'geneID'][1:10]
  m2<-rna2.de.vals[[i]][rna2.de.vals[[i]]$log2FoldChange<(-1.5), 'geneID'][1:10]
  m<-which(rna2.de.vals[[i]]$geneID%in%union(m1,m2))
  for(j in m){
    if(abs(rna2.de.vals[[i]]$log2FoldChange[j])>1.5&rna2.de.vals[[i]]$padj[j]<0.1){
      rna2.de.vals[[i]]$label[j]<-TRUE
    }
  }
  vol.plots[[i]]<-ggplot(rna2.de.vals[[i]], aes( x=log2FoldChange, y=(-log10(padj)), color=Group))+scale_colour_manual(values = c(color.combo[,i], 'gray70'))+geom_point(size=0.24)+geom_text_repel(data = rna2.de.vals[[i]][which(rna2.de.vals[[i]]$label),], aes( x=log2FoldChange, y=(-log10(padj)), label=geneID), size=1.76, show.legend=FALSE)+theme_pub+geom_vline(xintercept = 1.5, size=0.24)+geom_vline(xintercept = -1.5, size=0.24)+geom_hline(yintercept = 1, size=0.24)+xlab("log2 fold change")+ylab("-log10 p-adjusted value")+guides(color = guide_legend(override.aes = list(size=1.5)))
}

vol.plots[[1]] + vol.plots[[2]] +vol.plots[[3]] + plot_annotation(tag_levels = 'A')+plot_layout(ncol = 2)

NA
NA
NA

4.7 Hierarchical clustering on final day using DEGs

We want to check whether information about diet groups prior to switch can be retrieved at day 20 - i.e 13 days after the switch. For this, we use diet signature genes identified before the switch (DEGs) to hierarchically cluster the groups. We can almost perfectly classify groups using Record-seq data, while for RNA-seq, the groups converge.

rec2d_day20<-rec2d[rec2d$Day==20,]
rec2_day20<-rec2[,rownames(rec2d_day20)]
rec2_day20_tf<-recoRdseq.transform(rec2_day20, rec2d_day20)

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
ribosomal<-c(grep("rrs", rec2.global.DEG$geneID), grep("rrl", rec2.global.DEG$geneID))
if(length(ribosomal)>0){
  rec2.global.DEG<-rec2.global.DEG[-ribosomal,]
}
rec2.geneShortList<-unique(c(rec2.global.DEG[which(rec2.global.DEG$log2FoldChange.max_FC>2.5), 1][1:10], rec2.global.DEG[which(rec2.global.DEG$log2FoldChange.max_SC>2.5), 1][1:10],rec2.global.DEG[which(rec2.global.DEG$log2FoldChange.max_SC<(-2.5)&rec2.global.DEG$log2FoldChange.max_FC<(-2.5)), 1][1:10]))
dheatmap<-as.data.frame(t(apply(rec2_day20_tf[rec2.geneShortList,], 1, zscorestandardize)))
heatmap.rec2<-pheatmap(dheatmap, annotation_col = rec2d_day20[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE,color = cols,fontsize_number=5, main='Hierarchical clustering of Record-seq data on day 20 based on diet signature genes')

heatmap.genelist<-rec2_day20_tf[rec2.geneShortList,]
colnames(heatmap.genelist)<-as.character(rec2d_day20$Diet)

rna2d_day20<-rna2d[rna2d$Day==20,]
rna2_day20<-rna2[,rownames(rna2d_day20)]
rna2_day20_tf<-recoRdseq.transform(rna2_day20, rna2d_day20)
rna2.geneShortList<-unique(c(rna2.DEG[rna2.DEG$log2FoldChange.Fat_vs_Chow>2.5, 1][1:10], rna2.DEG[rna2.DEG$log2FoldChange.Starch_vs_Chow>2.5, 1][1:10], rna2.DEG[which(rowMeans(rna2.DEG[,3:4])<(-2.5)), 1][1:10]))
cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rna2_day20_tf[rna2.geneShortList,], 1, zscorestandardize)))
heatmap.rna2<-pheatmap(dheatmap, annotation_col = rna2d_day20[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE, color = cols,fontsize_number=5, main='Hierarchical clustering of RNA-seq data on day 20 based on diet signature genes')

heatmap.genelist<-rna2_day20_tf[rna2.geneShortList,]
colnames(heatmap.genelist)<-as.character(rna2d_day20$Diet)

4.8 Ecocyc analysis:

We create enrichment plots for top differentially regulated pathways identified by Ecocyc using the pairwise DEG lists generated here.

ChowXStarch.pathway<-as.data.frame(read.table("data/Chow.Starch.pathway.txt", header=TRUE, sep = '\t'))
ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Chow']<-log10(ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Chow'])*(-1)
ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Starch']<-log10(ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Starch'])
ChowXStarch.pathway<-ChowXStarch.pathway[order(ChowXStarch.pathway$p.values),]
ChowXStarch.pathway.plot<-ggplot(ChowXStarch.pathway, aes(x=Pathway, y=p.values, size=number.of.genes, colour=group ))+geom_point()+coord_flip()+theme_pub+scale_size_continuous(range = c(1,3))+geom_hline(yintercept = (-1.30103), size=0.24)+geom_hline(yintercept = 0, size=0.24)+geom_hline(yintercept = 1.30103, size=0.24)+xlab("")+ylab("-log10 p-adjusted value")+ labs(size = "Genes detected")+scale_x_discrete(limits=ChowXStarch.pathway$Pathway)+scale_color_manual(values = c(as.character(colour_code$Diet[c(1,3)])))

ChowXStarch.pathway.plot+plot_annotation()


ChowXFat.pathway<-as.data.frame(read.table("data/Chow.Fat.pathway.txt", header=TRUE, sep = '\t'))
ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Chow']<-log10(ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Chow'])*(-1)
ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Fat']<-log10(ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Fat'])
ChowXFat.pathway<-ChowXFat.pathway[order(ChowXFat.pathway$p.values),]
ChowXFat.pathway.plot<-ggplot(ChowXFat.pathway, aes(x=Pathway, y=p.values, size=number.of.genes, colour=group ))+geom_point()+coord_flip()+theme_pub+scale_size_continuous(range = c(1,3))+geom_hline(yintercept = (-1.30103), size=0.24)+geom_hline(yintercept = 0, size=0.24)+geom_hline(yintercept = 1.30103, size=0.24)+xlab("")+ylab("-log10 p-adjusted value")+ labs(size = "Genes detected")+scale_x_discrete(limits=ChowXFat.pathway$Pathway)+scale_color_manual(values = c(as.character(colour_code$Diet[c(1,2)])))

ChowXFat.pathway.plot+plot_annotation()


FatXStarch.pathway<-as.data.frame(read.table("data/Fat.Starch.pathway.txt", header=TRUE, sep = '\t'))
FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Fat']<-log10(FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Fat'])*(-1)
FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Starch']<-log10(FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Starch'])
FatXStarch.pathway<-FatXStarch.pathway[order(FatXStarch.pathway$p.values),]
FatXStarch.pathway.plot<-ggplot(FatXStarch.pathway, aes(x=Pathway, y=p.values, size=number.of.genes, colour=group ))+geom_point()+coord_flip()+theme_pub+scale_size_continuous(range = c(1,3))+geom_hline(yintercept = (-1.30103), size=0.24)+geom_hline(yintercept = 0, size=0.24)+geom_hline(yintercept = 1.30103, size=0.24)+xlab("")+ylab("-log10 p-adjusted value")+ labs(size = "Genes detected")+scale_x_discrete(limits=FatXStarch.pathway$Pathway)+scale_color_manual(values = c(as.character(colour_code$Diet[c(2,3)])))

FatXStarch.pathway.plot+plot_annotation()

NA
NA

5 Checking reproducibility of classifier genes (DEGs)

We want to confirm that there is an overlap among the DEGs identified in the two experimental replicates, and the direction of differential regulation is consistent. We use the genes that are upregulated/downregulated in the Chow group compared to the Starch group on day 7.

deseq.genes<-recoRdseq.filterDEG(rec1.de.vals[[2]], p = 0.1)
edger.genes<-recoRdseq.filterDEG(rec1.ed.vals[[2]], p = 0.1)
rec1.Chow.v.Starch.DEG<-data.frame(row.names = intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes), log2FC=rec1.de.vals[[2]][intersect(deseq.genes, edger.genes),4])

deseq.genes<-recoRdseq.filterDEG(rec2.de.vals[[2]], p = 0.1)
edger.genes<-recoRdseq.filterDEG(rec2.ed.vals[[2]], p = 0.1)
rec2.Chow.v.Starch.DEG<-data.frame(row.names = intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes), log2FC=rec2.de.vals[[2]][intersect(deseq.genes, edger.genes),4])

plot(euler(list(rec1 = rec1.Chow.v.Starch.DEG$geneID, rec2 = rec2.Chow.v.Starch.DEG$geneID)) , quantities=TRUE)

Finally, we plot a correlation plot based on the log2FC detected for DEGs for the two experiments, and estimate the number of DEGs regulated in a similar direction.

 DEG.compare<-data.frame(geneID=intersect(rec1.Chow.v.Starch.DEG$geneID, rec2.Chow.v.Starch.DEG$geneID))
DEG.compare$geneID<-as.character(DEG.compare$geneID)
DEG.compare$rec1_log2FC<-rec1.Chow.v.Starch.DEG[DEG.compare$geneID,2]
DEG.compare$rec2_log2FC<-rec2.Chow.v.Starch.DEG[DEG.compare$geneID,2]
DEG.compare<-DEG.compare[complete.cases(DEG.compare), ]
r2<-round(cor(DEG.compare$rec1_log2FC, DEG.compare$rec2_log2FC)^2,2)
n<-round(length(which(DEG.compare$rec1_log2FC*DEG.compare$rec2_log2FC>0))*100/length(DEG.compare$geneID))
DEG.scatterplot<-ggplot(DEG.compare, aes(y=rec1_log2FC, x=rec2_log2FC))+geom_point(size=0.48, aes(colour='gray10'))+geom_smooth(method = 'lm', se = FALSE, size=0.48)+theme_pub+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+xlab("log2 fold change of DEGs detected in transient diet 2")+ylab("corresponding log2 fold change in transient diet 1")+annotate("text",  x=Inf, y = Inf, label = paste0("R^2 =",as.character(r2), " \n DEGs regulated in the same direction = ",as.character(n), "%"), vjust=1, hjust=1, size=3)+scale_color_manual(values = c('gray10'), guide='none')+ggtitle("Correlation of overlapping DEGs detected in both experiments")
DEG.scatterplot+plot_annotation()

6 Information about R session

sessionInfo()
R version 4.0.3 (2020-10-10)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS  12.5.1

Matrix products: default
LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
 [1] parallel  stats4    grid      stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] ggrepel_0.9.1               patchwork_1.1.1             factoextra_1.0.7            recoRdseq_0.2              
 [5] umap_0.2.8.0                RColorBrewer_1.1-3          gplots_3.1.3                reshape2_1.4.4             
 [9] baySeq_2.24.0               abind_1.4-5                 edgeR_3.32.1                DESeq2_1.30.1              
[13] SummarizedExperiment_1.20.0 Biobase_2.50.0              MatrixGenerics_1.2.1        matrixStats_0.62.0         
[17] GenomicRanges_1.42.0        GenomeInfoDb_1.26.7         IRanges_2.24.1              S4Vectors_0.28.1           
[21] BiocGenerics_0.36.1         cluster_2.1.3               ggfortify_0.4.14            pheatmap_1.0.12            
[25] VennDiagram_1.7.3           futile.logger_1.4.3         plyr_1.8.7                  eulerr_6.1.1               
[29] devtools_2.4.3              usethis_2.1.5               forcats_0.5.1               stringr_1.4.0              
[33] dplyr_1.0.9                 purrr_0.3.4                 readr_2.1.2                 tidyr_1.2.0                
[37] tibble_3.1.7                tidyverse_1.3.1             ggplot2_3.3.6               randtests_1.0.1            
[41] limma_3.46.0                readxl_1.4.0               

loaded via a namespace (and not attached):
  [1] backports_1.4.1        systemfonts_1.0.4      polylabelr_0.2.0       splines_4.0.3          BiocParallel_1.24.1   
  [6] digest_0.6.29          htmltools_0.5.2        fansi_1.0.3            magrittr_2.0.3         memoise_2.0.1         
 [11] tzdb_0.3.0             remotes_2.4.2          annotate_1.68.0        modelr_0.1.8           bdsmatrix_1.3-4       
 [16] askpass_1.1            prettyunits_1.1.1      colorspace_2.0-3       apeglm_1.12.0          blob_1.2.3            
 [21] rvest_1.0.2            textshaping_0.3.6      haven_2.5.0            xfun_0.30              callr_3.7.0           
 [26] crayon_1.5.1           RCurl_1.98-1.6         jsonlite_1.8.0         genefilter_1.72.1      survival_3.3-1        
 [31] glue_1.6.2             polyclip_1.10-0        gtable_0.3.0           zlibbioc_1.36.0        XVector_0.30.0        
 [36] DelayedArray_0.16.3    pkgbuild_1.3.1         scales_1.2.0           mvtnorm_1.1-3          futile.options_1.0.1  
 [41] DBI_1.1.2              Rcpp_1.0.8.3           emdbook_1.3.12         xtable_1.8-4           reticulate_1.25       
 [46] bit_4.0.4              httr_1.4.3             ellipsis_0.3.2         pkgconfig_2.0.3        XML_3.99-0.9          
 [51] farver_2.1.0           dbplyr_2.1.1           locfit_1.5-9.4         utf8_1.2.2             tidyselect_1.1.2      
 [56] labeling_0.4.2         rlang_1.0.2            AnnotationDbi_1.52.0   munsell_0.5.0          cellranger_1.1.0      
 [61] tools_4.0.3            cachem_1.0.6           cli_3.3.0              generics_0.1.2         RSQLite_2.2.11        
 [66] broom_0.8.0            evaluate_0.15          fastmap_1.1.0          yaml_2.3.5             ragg_1.2.2            
 [71] processx_3.5.3         knitr_1.39             bit64_4.0.5            fs_1.5.2               caTools_1.18.2        
 [76] nlme_3.1-157           formatR_1.12           xml2_1.3.3             brio_1.1.3             compiler_4.0.3        
 [81] rstudioapi_0.13        png_0.1-7              testthat_3.1.4         reprex_2.0.1           geneplotter_1.68.0    
 [86] stringi_1.7.6          ps_1.7.0               RSpectra_0.16-0        desc_1.4.1             lattice_0.20-45       
 [91] Matrix_1.4-1           vctrs_0.4.1            pillar_1.7.0           lifecycle_1.0.1        bitops_1.0-7          
 [96] R6_2.5.1               KernSmooth_2.23-20     gridExtra_2.3          sessioninfo_1.2.2      lambda.r_1.2.4        
[101] MASS_7.3-56            gtools_3.9.2           assertthat_0.2.1       pkgload_1.2.4          openssl_2.0.0         
[106] rprojroot_2.0.3        withr_2.5.0            GenomeInfoDbData_1.2.4 mgcv_1.8-40            hms_1.1.1             
[111] coda_0.19-4            rmarkdown_2.14         bbmle_1.0.24           numDeriv_2016.8-1.1    lubridate_1.8.0       
LS0tCnRpdGxlOiAiVHJhbnNpZW50IERpZXRzIEkgKyBJSSBhbmFseXNpcyAtIFJOQS1zZXEgJiBSZWNvcmQtc2VxIgprbml0OiAoZnVuY3Rpb24oaW5wdXRfZmlsZSwgZW5jb2RpbmcpIHsKICAgIG91dF9kaXIgPC0gJy4uLy4uL2RvY3MnOwogICAgcm1hcmtkb3duOjpyZW5kZXIoaW5wdXRfZmlsZSwKICAgICAgZW5jb2Rpbmc9ZW5jb2RpbmcsCiAgICAgIG91dHB1dF9maWxlPWZpbGUucGF0aChkaXJuYW1lKGlucHV0X2ZpbGUpLCBvdXRfZGlyLCAnaW5kZXguaHRtbCcpKX0pCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQotLS0KIyBJbnRyb2R1Y3Rpb24gCgpUaGlzIFIgbm90ZWJvb2sgaW1wbGVtZW50cyB0aGUgYW5hbHlzaXMgb2YgdGhlIFJlY29yZC1zZXEgYW5kIFJOQS1zZXEgcmVhZG91dCBvZiB0aGUgdHJhbnNpZW50IGRpZXQgZXhwZXJpbWVudHMgZGVzY3JpYmVkIGluICdOb25pbnZhc2l2ZSBhc3Nlc3NtZW50IG9mIGd1dCBmdW5jdGlvbiB1c2luZyB0cmFuc2NyaXB0aW9uYWwgcmVjb3JkaW5nIHNlbnRpbmVsIGNlbGxzJyBtYW51c2NyaXB0LiBUaGUgZm9sbG93aW5nIGZpbGVzIGFyZSBzdG9yZWQgaW4gdGhlIGBkYXRhYCBkaXJlY3Rvcnkgd2l0aGluIHRoZSB3b3JraW5nIGRpcmVjdG9yeTogCgogICAgdHJhbnNpZW50LWRpZXQvCiAgICAgICAgc2Vjb25kYXJ5QW5hbHlzaXMuUm1kCiAgICAgICAgZGF0YS8KICAgICAgICAgICAgdHJhbnNpZW50RGlldDFfUmVjb3Jkc2VxX2dlbm9tZWFsaWduaW5nLnR4dAogICAgICAgICAgICB0cmFuc2llbnREaWV0MV9SZWNvcmRzZXFfbWV0YWRhdGEudHh0CiAgICAgICAgICAgIHRyYW5zaWVudERpZXQxX1JOQXNlcV9nZW5vbWVhbGlnbmluZy50eHQKICAgICAgICAgICAgdHJhbnNpZW50RGlldDFfUk5Bc2VxX21ldGFkYXRhLnR4dAogICAgICAgICAgICB0cmFuc2llbnREaWV0MV9kYXkxNF9STkFzZXFfZ2Vub21lYWxpZ25pbmcudHh0CiAgICAgICAgICAgIHRyYW5zaWVudERpZXQxX2RheTE0X1JOQXNlcV9tZXRhZGF0YS50eHQgIAogICAgICAgICAgICB0cmFuc2llbnREaWV0Ml9SZWNvcmRzZXFfZ2Vub21lYWxpZ25pbmcudHh0CiAgICAgICAgICAgIHRyYW5zaWVudERpZXQyX1JlY29yZHNlcV9tZXRhZGF0YS50eHQKICAgICAgICAgICAgdHJhbnNpZW50RGlldDJfUk5Bc2VxX2dlbm9tZWFsaWduaW5nLnR4dAogICAgICAgICAgICB0cmFuc2llbnREaWV0Ml9STkFzZXFfbWV0YWRhdGEudHh0CiAgICAgICAgICAgIENob3cuRmF0LnBhdGh3YXkudHh0CiAgICAgICAgICAgIENob3cuU3RhcmNoLnBhdGh3YXkudHh0CiAgICAgICAgICAgIEZhdC5TdGFyY2gucGF0aHdheS50eHQKICAgICAgICAKIyBMaWJyYXJpZXMKClRoZSBgcmVjb1Jkc2VxYCBwYWNrYWdlIGFuZCBkZXBlbmRlbmNpZXMgYXJlIHJlcXVpcmVkIGZvciB0aGlzIGFuYWx5c2lzLCBhbmQgdGhlIGZhbnRhc3RpYyBgcGF0Y2h3b3JrYCBwYWNrYWdlIGlzIHVzZWQgZm9yIHZpc3VhbGl6YXRpb24uIAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KaWYoIXJlcXVpcmUoZGV2dG9vbHMpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpCn0KbGlicmFyeShkZXZ0b29scykKaWYoIXJlcXVpcmUoZXVsZXJyKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZXVsZXJyIikKfQpsaWJyYXJ5KGV1bGVycikKaWYoIXJlcXVpcmUocGx5cikpewogIGluc3RhbGwucGFja2FnZXMoInBseXIiKQp9CmxpYnJhcnkocGx5cikKaWYoIXJlcXVpcmUocmVjb1Jkc2VxKSl7CiAgaW5zdGFsbF9naXRodWIoInBsYXR0bGFiL1RyYW5zY3JpcHRpb25hbC1SZWNvcmRpbmciLCBzdWJkaXI9InJlY29SZHNlcSIpCn0KaWYoIXJlcXVpcmUoZmFjdG9leHRyYSkpewogIGluc3RhbGwucGFja2FnZXMoImZhY3RvZXh0cmEiKQp9CmxpYnJhcnkoZmFjdG9leHRyYSkKaWYoIXJlcXVpcmUocGF0Y2h3b3JrKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygncGF0Y2h3b3JrJykKfQppZighcmVxdWlyZShnZ3JlcGVsKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygnZ2dyZXBlbCcpCn0KbGlicmFyeShyZWNvUmRzZXEpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoZ2dyZXBlbCkKY29sb3VyX2NvZGUgPSBsaXN0KAogIERpZXQgPSBjKENob3cgPSAiIzUzOGJjZSIsIEZhdD0iI2VkOTE1YyIsIFN0YXJjaD0nIzQyYmI3ZicpKSAjIHdlIHNldCBhIGNvbnNpc3RlbnQgY29sb3Igc2NoZW1lIGZvciB0aGUgdGhyZWUgZGlldCBncm91cHMKCnRoZW1lX3B1YjwtdGhlbWVfbWluaW1hbCgpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIiwgbGVnZW5kLmp1c3RpZmljYXRpb249ImNlbnRlciIsIGxlZ2VuZC5tYXJnaW49bWFyZ2luKDAsMCwwLDApLGxlZ2VuZC5ib3gubWFyZ2luPW1hcmdpbigtMTAsLTEwLC0xMCwtMTApLHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLCBsZWdlbmQuc3BhY2luZy55ID0gIHVuaXQoMCwgJ21tJyksIGxlZ2VuZC5ib3g9J3ZlcnRpY2FsJywgbGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjEsICJjbSIpLGxlZ2VuZC5rZXkud2lkdGggPSB1bml0KDAuMSwiY20iKSwgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9NSksIHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT01KSwgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9NSwgY29sb3VyPSdibGFjaycpLCBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKHNpemUgPSAwLjI0LCBjb2xvdXI9J2dyYXkxJywgbGluZXR5cGUgPSAyKSkgIyB3ZSBzZXQgYSBjb25zaXN0ZW50IHRoZW1lIGZvciBnZ3Bsb3Qgb2JqZWN0cwoKY3VzdG9tLmNvbmZpZyA9IHVtYXAuZGVmYXVsdHMKY3VzdG9tLmNvbmZpZyRyYW5kb21fc3RhdGUgPSAyCmBgYAojIFRyYW5zaWVudCBEaWV0IDEKCkRhdGEgZm9yIHRoZSB0cmFuc2llbnQgZGlldCBleHBlcmltZW50IHdpdGggMTQgZGF5cyBpcyBhbmFseXplZCBmaXJzdC4KCiMjIEltcG9ydGluZyBhbmQgcHJlLXByb2Nlc3NpbmcgZGF0YSBmb3IgdHJhbnNpZW50IGRpZXQgMQoKICBXZSBpbXBvcnQgdGhlIGRhdGEgbWF0cmljZXMgZm9yIGJvdGggUmVjb3JkLXNlcSBhbmQgUk5BLXNlcSwgZmlsdGVyIHRoZW0gZm9yIGxvd2x5IGV4cHJlc3NlZCBnZW5lcyBhcyB3ZWxsIGFzIG91dGxpZXIgc2FtcGxlcyB3aXRoIGxvdyBjdW11bGF0aXZlIGNvdW50cywgYW5kIHVzZSB2c3QgZnJvbSBfREVTZXEyXyB0byBub3JtYWxpemUgYW5kIHRyYW5zZm9ybSB0aGUgZGF0YS4gV2UgdXNlIGEgdGhyZXNob2xkIG9mIDEwayBjb3VudHMgZm9yIGV4Y2x1ZGluZyBSZWNvcmQtc2VxIHNhbXBsZXMgYW5kIDEwMGsgY291bnRzIGZvciBleGNsdWRpbmcgUk5BLXNlcSBzYW1wbGVzLiAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJlYzE8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS90cmFuc2llbnREaWV0MV9SZWNvcmRzZXFfZ2Vub21lYWxpZ25pbmcudHh0IiwgaGVhZGVyID0gVFJVRSkpCnJlYzFkPC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvdHJhbnNpZW50RGlldDFfUmVjb3Jkc2VxX21ldGFkYXRhLnR4dCIsIGhlYWRlciA9IFRSVUUpKQpERUxpc3Q8LXJlY29SZHNlcS5wcmVwcm9jZXNzKHJlYzEsIHJlYzFkLCBtaW5Db3VudHNQZXJTYW1wbGUgPSAxMDAwMCkKcmVjMTwtREVMaXN0W1sxXV0KcmVjMWQ8LURFTGlzdFtbMl1dCnJlYzFkPC1yZWMxZFtyZWMxZCREYXk+MSxdCnJlYzE8LXJlYzFbLHJvd25hbWVzKHJlYzFkKV0KcmVjMV90ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShyZWMxLCByZWMxZCx0cmFuc2Zvcm1hdGlvbiA9ICd2c3QnKQoKcm5hMTwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL3RyYW5zaWVudERpZXQxX1JOQXNlcV9nZW5vbWVhbGlnbmluZy50eHQiLCBoZWFkZXIgPSBUUlVFKSkKcm5hMWQ8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS90cmFuc2llbnREaWV0MV9STkFzZXFfbWV0YWRhdGEudHh0IiwgaGVhZGVyID0gVFJVRSkpCkRFTGlzdDwtcmVjb1Jkc2VxLnByZXByb2Nlc3Mocm5hMSwgcm5hMWQsIG1pbkNvdW50c1BlclNhbXBsZSA9IDEwMDAwMCkKcm5hMTwtREVMaXN0W1sxXV0Kcm5hMWQ8LURFTGlzdFtbMl1dCnJuYTFfdGY8LXJlY29SZHNlcS50cmFuc2Zvcm0ocm5hMSwgcm5hMWQpCnJuYWRheXM8LXVuaXF1ZShybmExZCREYXkpCnJuYTFfZGF5MTQ8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS90cmFuc2llbnREaWV0MV9kYXkxNF9STkFzZXFfZ2Vub21lYWxpZ25pbmcudHh0IiwgaGVhZGVyID0gVFJVRSkpCnJuYTFkX2RheTE0PC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvdHJhbnNpZW50RGlldDFfZGF5MTRfUk5Bc2VxX21ldGFkYXRhLnR4dCIsIGhlYWRlciA9IFRSVUUpKQpybmExZF9kYXkxNDwtcm5hMWRfZGF5MTRbLDE6M10KREVMaXN0PC1yZWNvUmRzZXEucHJlcHJvY2VzcyhybmExX2RheTE0LCBybmExZF9kYXkxNCwgbWluQ291bnRzUGVyU2FtcGxlID0gMTAwMDAwKQpybmExX2RheTE0PC1ERUxpc3RbWzFdXQpybmExZF9kYXkxNDwtREVMaXN0W1syXV0Kcm5hMV9kYXkxNHRmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJuYTFfZGF5MTQsIHJuYTFkX2RheTE0LCB0cmFuc2Zvcm1hdGlvbiA9ICd2c3QnKQoKYGBgCgojIyBEYXRhIGV4cGxvcmF0aW9uCiAgV2UgdXNlIFByaW5jaXBhbCBDb21wb25lbnQgYW5hbHlzaXMgYW5kIFVNQVAgZm9yIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBhbmQgZXhwbG9yaW5nIGNsdXN0ZXJzIGluIGFuIHVuc3VwZXJ2aXNlZCBmYXNoaW9uIGluIG91ciBkYXRhLiAgV2UgZmlyc3QgZ2VuZXJhdGUgdGhlc2UgZm9yIHRoZSBlbnRpcmUgZGF0YXNldCBmcm9tIFJlY29yZC1zZXE6CgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCAgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQpyZWMxc2RzIDwtIHJvd1Nkcyhhcy5tYXRyaXgocmVjMV90ZikpCm8gPC0gb3JkZXIocmVjMXNkcywgZGVjcmVhc2luZyA9IFRSVUUpCnJlYzFQQ0E8LXByY29tcCh0KHJlYzFfdGZbb1sxOjUwMF0sXSkpCnBjYV9zdGF0PC1zdW1tYXJ5KHJlYzFQQ0EpCnBjYV92YXJpYW5jZTwtcGNhX3N0YXQkaW1wb3J0YW5jZVsyLF0KcmVjMVBDQTwtYXMuZGF0YS5mcmFtZShyZWMxUENBJHgpCnJlYzFQQ0EkRGlldDwtcmVjMWQkRGlldApyZWMxUENBJERheTwtZmFjdG9yKHJlYzFkJERheSwgbGV2ZWxzID0gMToyMCkKcmVjMVBDQXBsb3Q8LWdncGxvdChyZWMxUENBLCBhZXMoeD1QQzEsIHk9UEMyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygxLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSt5bGFiKHBhc3RlMCgiUEMyICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzJdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkreGxhYihwYXN0ZTAoIlBDMSAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsxXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK2dndGl0bGUoIiBQQ0EgcGxvdCBvZiBSZWNvcmQtc2VxIGRhdGEgLSBhbGwgZGF5cyAodHJhbnNpZW50IGRpZXQgMSkiKQoKCnJlYzFVTUFQPC11bWFwKHJlYzFQQ0FbLDE6KG5jb2wocmVjMVBDQSktMildLCBjdXN0b20uY29uZmlnKQpyZWMxVU1BUDwtYXMuZGF0YS5mcmFtZShyZWMxVU1BUCRsYXlvdXQpCnJlYzFVTUFQJERheTwtZmFjdG9yKHJlYzFkJERheSwgbGV2ZWxzID0gMjoxNCkKcmVjMVVNQVAkRGlldDwtcmVjMWQkRGlldApjb2xuYW1lcyhyZWMxVU1BUClbMToyXTwtYygnVU1BUDEnLCdVTUFQMicpCnJlYzFVTUFQcGxvdDwtZ2dwbG90KHJlYzFVTUFQLCBhZXMoeD1VTUFQMSwgeT1VTUFQMiwgZmlsbD1EaWV0LCAgc2l6ZT1EYXkpKStnZW9tX3BvaW50KHBjaD0yMSwgY29sb3VyPScjMDAwMDAwJywgc3Ryb2tlPTAuMjQpK3RoZW1lX3B1YitzY2FsZV9zaXplX2Rpc2NyZXRlKHJhbmdlPWMoMSwyLjUpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0KSkrZ2d0aXRsZSgiVU1BUCBwbG90IGZvciBSZWNvcmQtc2VxIGRhdGEgLSBhbGwgZGF5cyAodHJhbnNpZW50IGRpZXQgMSkiKQoKcmVjMVBDQXBsb3QrcmVjMVVNQVBwbG90K3Bsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKQoKYGBgCgogIEZvciBhIGNvbXBhcmlzb24gYmV0d2VlbiBSZWNvcmQtc2VxIGFuZCBSTkEtc2VxLCB3ZSBjaGVjayBpZiB0aGUgZGlldCBncm91cHMgY2FuIGJlIGNsYXNzaWZpZWQgdXNpbmcgUENBIG9uIGRheSA3ICh0aGUgbGFzdCBkYXkgd2hlbiB0aGUgbWljZSBhcmUgZmVkIGRpZmZlcmVudCBkaWV0cywgYmVmb3JlIHN3aXRjaGluZyBhbGwgbWljZSB0byBhICdDaG93JyBkaWV0KQoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQoKcmVjMWRfZGF5NzwtcmVjMWRbcmVjMWQkRGF5PT03LF0KcmVjMV9kYXk3PC1yZWMxWywgcm93bmFtZXMocmVjMWRfZGF5NyldCnJlYzFfZGF5N3RmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJlYzFfZGF5NywgcmVjMWRfZGF5NykKcmVjMV9kYXk3X3NkcyA8LSByb3dTZHMoYXMubWF0cml4KHJlYzFfZGF5N3RmKSkKbyA8LSBvcmRlcihyZWMxX2RheTdfc2RzLCBkZWNyZWFzaW5nID0gVFJVRSkKcmVjMV9kYXk3UENBPC1wcmNvbXAodChyZWMxX2RheTd0ZltvWzE6NTAwXSxdKSkKcGNhX3N0YXQ8LXN1bW1hcnkocmVjMV9kYXk3UENBKQpwY2FfdmFyaWFuY2U8LXBjYV9zdGF0JGltcG9ydGFuY2VbMixdCnJlYzFfZGF5N1BDQTwtYXMuZGF0YS5mcmFtZShyZWMxX2RheTdQQ0EkeCkKcmVjMV9kYXk3UENBJERpZXQ8LXJlYzFkX2RheTckRGlldApyZWMxX2RheTdQQ0EkRGF5PC1mYWN0b3IocmVjMWRfZGF5NyREYXksIGxldmVscyA9IGMoNykpCnJlYzFfZGF5N1BDQXBsb3Q8LWdncGxvdChyZWMxX2RheTdQQ0EsIGFlcyh4PVBDMSwgeT1QQzIsIGZpbGw9RGlldCwgIHNpemU9RGF5KSkrZ2VvbV9wb2ludChwY2g9MjEsIGNvbG91cj0nIzAwMDAwMCcsIHN0cm9rZT0wLjI1KSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9kaXNjcmV0ZShyYW5nZT1jKDIsMi41KSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldCkpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3lsYWIocGFzdGUwKCJQQzIgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMl0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKSt4bGFiKHBhc3RlMCgiUEMxICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzFdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkrZ2d0aXRsZSgiIFBDQSBwbG90IG9mIFJlY29yZC1zZXEgZGF0YSBvbiBkYXkgNyAodHJhbnNpZW50IGRpZXQgMSkiKQoKcm5hMV9zZHMgPC0gcm93U2RzKGFzLm1hdHJpeChybmExX3RmKSkKbyA8LSBvcmRlcihybmExX3NkcywgZGVjcmVhc2luZyA9IFRSVUUpCnJuYTFfUENBPC1wcmNvbXAodChybmExX3RmW29bMTo1MDBdLF0pKQpwY2Ffc3RhdDwtc3VtbWFyeShybmExX1BDQSkKcGNhX3ZhcmlhbmNlPC1wY2Ffc3RhdCRpbXBvcnRhbmNlWzIsXQpybmExX1BDQTwtYXMuZGF0YS5mcmFtZShybmExX1BDQSR4KQpybmExX1BDQSREaWV0PC1ybmExZCREaWV0CnJuYTFfUENBJERheTwtZmFjdG9yKHJuYTFkJERheSwgbGV2ZWxzID0gYyg3KSkKcm5hMV9kYXk3UENBcGxvdDwtZ2dwbG90KHJuYTFfUENBLCBhZXMoeD1QQzEsIHk9UEMyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygyLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSt5bGFiKHBhc3RlMCgiUEMyICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzJdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkreGxhYihwYXN0ZTAoIlBDMSAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsxXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK2dndGl0bGUoIiBQQ0EgcGxvdCBvZiBSTkEtc2VxIGRhdGEgb24gZGF5IDcgKHRyYW5zaWVudCBkaWV0IDEpICIpCgoKcm5hMV9kYXk3UENBcGxvdCtyZWMxX2RheTdQQ0FwbG90K3Bsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKQoKYGBgCgogIE5leHQsIHdlIGNoZWNrIGlmIFJlY29yZC1zZXEgb3IgUk5BLXNlcSBjYW4gZGlzdGluZ3Vpc2ggdGhlIHNhbXBsZXMgb24gZGF5IDE0ICg3IGRheXMgYWZ0ZXIgc3dpdGNoaW5nIGFsbCBzYW1wbGVzIHRvIENob3cgZGlldCkuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CgpyZWMxZF9kYXkxNDwtcmVjMWRbcmVjMWQkRGF5PT0xNCxdCnJlYzFfZGF5MTQ8LXJlYzFbLCByb3duYW1lcyhyZWMxZF9kYXkxNCldCnJlYzFfZGF5MTR0ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShyZWMxX2RheTE0LCByZWMxZF9kYXkxNCkKcmVjMV9kYXkxNF9zZHMgPC0gcm93U2RzKGFzLm1hdHJpeChyZWMxX2RheTE0dGYpKQpvIDwtIG9yZGVyKHJlYzFfZGF5MTRfc2RzLCBkZWNyZWFzaW5nID0gVFJVRSkKcmVjMV9kYXkxNFBDQTwtcHJjb21wKHQocmVjMV9kYXkxNHRmW29bMTo1MDBdLF0pKQpwY2Ffc3RhdDwtc3VtbWFyeShyZWMxX2RheTE0UENBKQpwY2FfdmFyaWFuY2U8LXBjYV9zdGF0JGltcG9ydGFuY2VbMixdCnJlYzFfZGF5MTRQQ0E8LWFzLmRhdGEuZnJhbWUocmVjMV9kYXkxNFBDQSR4KQpyZWMxX2RheTE0UENBJERpZXQ8LXJlYzFkX2RheTE0JERpZXQKcmVjMV9kYXkxNFBDQSREYXk8LWZhY3RvcihyZWMxZF9kYXkxNCREYXksIGxldmVscyA9IGMoMTQpKQpyZWMxX2RheTE0UENBcGxvdDwtZ2dwbG90KHJlYzFfZGF5MTRQQ0EsIGFlcyh4PVBDMSwgeT1QQzIsIGZpbGw9RGlldCwgIHNpemU9RGF5KSkrZ2VvbV9wb2ludChwY2g9MjEsIGNvbG91cj0nIzAwMDAwMCcsIHN0cm9rZT0wLjI1KSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9kaXNjcmV0ZShyYW5nZT1jKDIsMi41KSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldCkpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3lsYWIocGFzdGUwKCJQQzIgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMl0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKSt4bGFiKHBhc3RlMCgiUEMxICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzFdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkrZ2d0aXRsZSgiIFBDQSBwbG90IG9mIFJlY29yZC1zZXEgZGF0YSBvbiBkYXkgMTQgKHRyYW5zaWVudCBkaWV0IDEpIikKCnJuYTFfZGF5MTRfc2RzIDwtIHJvd1Nkcyhhcy5tYXRyaXgocm5hMV9kYXkxNHRmKSkKbyA8LSBvcmRlcihybmExX2RheTE0X3NkcywgZGVjcmVhc2luZyA9IFRSVUUpCnJuYTFfZGF5MTRfUENBPC1wcmNvbXAodChybmExX2RheTE0dGZbb1sxOjUwMF0sXSkpCnBjYV9zdGF0PC1zdW1tYXJ5KHJuYTFfZGF5MTRfUENBKQpwY2FfdmFyaWFuY2U8LXBjYV9zdGF0JGltcG9ydGFuY2VbMixdCnJuYTFfZGF5MTRfUENBPC1hcy5kYXRhLmZyYW1lKHJuYTFfZGF5MTRfUENBJHgpCnJuYTFfZGF5MTRfUENBJERpZXQ8LXJuYTFkX2RheTE0JERpZXQKcm5hMV9kYXkxNF9QQ0EkRGF5PC1mYWN0b3Iocm5hMWRfZGF5MTQkRGF5LCBsZXZlbHMgPSBjKDE0KSkKcm5hMV9kYXkxNFBDQXBsb3Q8LWdncGxvdChybmExX2RheTE0X1BDQSwgYWVzKHg9UEMxLCB5PVBDMiwgZmlsbD1EaWV0LCAgc2l6ZT1EYXkpKStnZW9tX3BvaW50KHBjaD0yMSwgY29sb3VyPScjMDAwMDAwJywgc3Ryb2tlPTAuMjUpK3RoZW1lX3B1YitzY2FsZV9zaXplX2Rpc2NyZXRlKHJhbmdlPWMoMiwyLjUpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0KSkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KSsgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTIpKSkreWxhYihwYXN0ZTAoIlBDMiAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsyXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK3hsYWIocGFzdGUwKCJQQzEgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMV0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKStnZ3RpdGxlKCIgUENBIHBsb3Qgb2YgUk5BLXNlcSBkYXRhIG9uIGRheSAxNCAodHJhbnNpZW50IGRpZXQgMSkgIikKCgpybmExX2RheTE0UENBcGxvdCtyZWMxX2RheTE0UENBcGxvdCtwbG90X2Fubm90YXRpb24odGFnX2xldmVscyA9ICdBJykKCmBgYAoKCiMjIERpc2NvdmVyeSBvZiBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMgKERFR3MpOgogIFdlIGlkZW50aWZ5IERFR3MgZm9yIGRheSA3IChzaW5jZSB0aGlzIGlzIHRoZSBmaW5hbCBkYXkgd2hlbiB0aGUgbWljZSBhcmUgZmVkIHNlcGFyYXRlIGRpZXRzLCBhbmQgUk5BLXNlcSBpcyBhbHNvIGF2YWlsYWJsZSBmb3IgdGhpcyBkYXkpLiBXZSBkZWZpbmUgREVHcyBhcyBnZW5lcyBpZGVudGlmaWVkIHRvIGJlIHNpZ25pZmljYW50bHkgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIHVzaW5nIGEgdGhyZXNob2xkIChwYWRqIDwgMC4wNSkgYnkgYm90aCBERVNlcTIgYW5kIGVkZ2VSIChtdWx0aXBsZSB0ZXN0aW5nLCBzaW5jZSB3ZSBoYXZlIDMgZ3JvdXBzKS4gCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpyZWMxLmRlc2VxPC1yZWNvUmRzZXEuREUocmVjMV9kYXk3LCByZWMxZF9kYXk3LCB0b29sPSdERVNlcTInKQpyZWMxLmVkZ2VyPC1yZWNvUmRzZXEuREUocmVjMV9kYXk3LCByZWMxZF9kYXk3LCB0b29sPSdlZGdlUicpCnJlYzEuZGVzZXEuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMS5kZXNlcSwgcCA9IDAuMDUpCnJlYzEuZWRnZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMS5lZGdlciwgcCA9IDAuMDUpCnJlYzEuREVHPC1yZWMxLmRlc2VxW2ludGVyc2VjdChyZWMxLmRlc2VxLmdlbmVzLCByZWMxLmVkZ2VyLmdlbmVzKSwgYygxLDcsIHdoaWNoKGdyZXBsKCdsb2cyRm9sZENoYW5nZScsIGNvbG5hbWVzKHJlYzEuZGVzZXEpKSkpXQpyZWMxLkRFRyRnZW5lSUQ8LWFzLmNoYXJhY3RlcihyZWMxLkRFRyRnZW5lSUQpCnJlYzEuREVHPC1yZWMxLkRFR1tvcmRlcihyZWMxLkRFRyRwYWRqKSxdCnJlYzEuREVHJGdlbmVJRDwtYXMuY2hhcmFjdGVyKHJlYzEuREVHJGdlbmVJRCkKcm5hMS5kZXNlcTwtcmVjb1Jkc2VxLkRFKHJuYTEsIHJuYTFkLCB0b29sPSdERVNlcTInKQpybmExLmVkZ2VyPC1yZWNvUmRzZXEuREUocm5hMSwgcm5hMWQsIHRvb2w9J2VkZ2VSJykKcm5hMS5kZXNlcS5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhybmExLmRlc2VxLCBwID0gMC4wNSkKcm5hMS5lZGdlci5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhybmExLmVkZ2VyLCBwID0gMC4wNSkKcm5hMS5ERUc8LXJuYTEuZGVzZXFbaW50ZXJzZWN0KHJuYTEuZGVzZXEuZ2VuZXMsIHJuYTEuZWRnZXIuZ2VuZXMpLCBjKDEsNywgd2hpY2goZ3JlcGwoJ2xvZzJGb2xkQ2hhbmdlJywgY29sbmFtZXMocm5hMS5kZXNlcSkpKSldCnJuYTEuREVHJGdlbmVJRDwtYXMuY2hhcmFjdGVyKHJuYTEuREVHJGdlbmVJRCkKCnJlYzEubm92ZWw8LXJlYzEuREVHWy13aGljaChyZWMxLkRFRyRnZW5lSUQlaW4lcm5hMS5ERUckZ2VuZUlEKSxdCmBgYAogIEZvciBSZWNvcmQtc2VxLCB3ZSBhbHNvIGxvb2sgZm9yIERFIGdlbmVzIG92ZXIgZGF5cyAyLTcgaW4gUmVjb3JkLXNlcSB1c2luZyBhIGxvb3NlciBjb25maWRlbmNlIHRocmVzaG9sZCAocGFkaiA8MC4xKSB0byBpZGVudGlmeSBjb25zaXN0ZW50IGRpZXQtc2lnbmF0dXJlIGdlbmVzLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcmVjMS5ERUcubGlzdDwtbGlzdCgpCnJlYzEuZXI8LWxpc3QoKQpyZWMxLmRlPC1saXN0KCkKcmVjMS5nbG9iYWwuREVHPC1jKCkKZm9yKGkgaW4gdW5pcXVlKHJlYzFkJERheSkpewogIGlmKGk8OCl7CiAgICBkdDwtcmVjMWRbd2hpY2gocmVjMWQkRGF5PT1pKSwgMSwgZHJvcD1GQUxTRV0KICAgIGRlPC1yZWMxWyx3aGljaChjb2xuYW1lcyhyZWMxKSVpbiVyb3duYW1lcyhkdCkpXQogICAgcmVjMS5kZVtbaV1dPC1yZWNvUmRzZXEuREUoZGUsZHQsdG9vbD0nREVTZXEyJykKICAgIHJlYzEuZXJbW2ldXTwtcmVjb1Jkc2VxLkRFKGRlLGR0LHRvb2w9J2VkZ2VSJykKICAgIHJlYzEuZGUuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMS5kZVtbaV1dLCBwID0gMC4xKQogICAgcmVjMS5lci5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMxLmVyW1tpXV0sIHAgPSAwLjEpCiAgICByZWMxLkRFRy5saXN0W1tpXV08LXJlYzEuZGVbW2ldXVtpbnRlcnNlY3QocmVjMS5kZS5nZW5lcywgcmVjMS5lci5nZW5lcyksIGMoMSw3LCB3aGljaChncmVwbCgnbG9nMkZvbGRDaGFuZ2UnLCBjb2xuYW1lcyhyZWMxLmRlW1tpXV0pKSkpXQogICAgcmVjMS5nbG9iYWwuREVHPC0gYyhyZWMxLmdsb2JhbC5ERUcsIGFzLmNoYXJhY3RlcihpbnRlcnNlY3QocmVjMS5kZS5nZW5lcyxyZWMxLmVyLmdlbmVzKSkpCiAgfQp9CnJlYzEuZ2xvYmFsLkRFRzE8LWFzLmRhdGEuZnJhbWUodGFibGUocmVjMS5nbG9iYWwuREVHKVtvcmRlcih0YWJsZShyZWMxLmdsb2JhbC5ERUcpLCBkZWNyZWFzaW5nID0gVFJVRSldKQpyZWMxLmdsb2JhbC5ERUcyPC1hcy5kYXRhLmZyYW1lKHRhYmxlKHJlYzEuZ2xvYmFsLkRFRylbb3JkZXIodGFibGUocmVjMS5nbG9iYWwuREVHKSwgZGVjcmVhc2luZyA9IFRSVUUpXSkKY29sbmFtZXMocmVjMS5nbG9iYWwuREVHMSk8LWMoImdlbmVJRCIsICJkYXlzX0RFIikKY29sbmFtZXMocmVjMS5nbG9iYWwuREVHMik8LWMoImdlbmVJRCIsICJkYXlzX0RFIikKcmVjMS5nbG9iYWwuREVHMSRnZW5lSUQ8LWFzLmNoYXJhY3RlcihyZWMxLmdsb2JhbC5ERUcxJGdlbmVJRCkKcmVjMS5nbG9iYWwuREVHMiRnZW5lSUQ8LWFzLmNoYXJhY3RlcihyZWMxLmdsb2JhbC5ERUcyJGdlbmVJRCkKZm9yKGkgaW4gdW5pcXVlKHJlYzFkJERheSkpewogIGlmKGk8OCl7CiAgcmVjMS5nbG9iYWwuREVHMSRWMTwtcmVjMS5kZVtbaV1dW3JlYzEuZ2xvYmFsLkRFRzEkZ2VuZUlELDRdCiAgY29sbmFtZXMocmVjMS5nbG9iYWwuREVHMSlbbmNvbChyZWMxLmdsb2JhbC5ERUcxKV08LXBhc3RlMCgibG9nMkZvbGRDaGFuZ2VfRkNfZGF5IiwgaSkKICByZWMxLmdsb2JhbC5ERUcyJFYxPC1yZWMxLmRlW1tpXV1bcmVjMS5nbG9iYWwuREVHMiRnZW5lSUQsOF0KICBjb2xuYW1lcyhyZWMxLmdsb2JhbC5ERUcyKVtuY29sKHJlYzEuZ2xvYmFsLkRFRzIpXTwtcGFzdGUwKCJsb2cyRm9sZENoYW5nZV9TQ19kYXkiLCBpKQoKICB9Cn0KcmVjMS5nbG9iYWwuREVHMSRsb2cyRm9sZENoYW5nZS5tYXg8LXJlYzEuZ2xvYmFsLkRFRzFbLDM6bmNvbChyZWMxLmdsb2JhbC5ERUcxKV1bY2JpbmQoMTpucm93KHJlYzEuZ2xvYmFsLkRFRzFbLDM6bmNvbChyZWMxLmdsb2JhbC5ERUcxKV0pLCBtYXguY29sKHJlcGxhY2UoeCA8LSBhYnMocmVjMS5nbG9iYWwuREVHMVssMzpuY29sKHJlYzEuZ2xvYmFsLkRFRzEpXSksIGlzLm5hKHgpLCAtSW5mKSkpXQpyZWMxLmdsb2JhbC5ERUcyJGxvZzJGb2xkQ2hhbmdlLm1heDwtcmVjMS5nbG9iYWwuREVHMlssMzpuY29sKHJlYzEuZ2xvYmFsLkRFRzIpXVtjYmluZCgxOm5yb3cocmVjMS5nbG9iYWwuREVHMlssMzpuY29sKHJlYzEuZ2xvYmFsLkRFRzIpXSksIG1heC5jb2wocmVwbGFjZSh4IDwtIGFicyhyZWMxLmdsb2JhbC5ERUcyWywzOm5jb2wocmVjMS5nbG9iYWwuREVHMildKSwgaXMubmEoeCksIC1JbmYpKSldCgpyZWMxLmdsb2JhbC5ERUc8LXJlYzEuZ2xvYmFsLkRFRzFbLGMoMSwyLG5jb2wocmVjMS5nbG9iYWwuREVHMSkpXQpyZWMxLmdsb2JhbC5ERUckVjE8LXJlYzEuZ2xvYmFsLkRFRzJbLG5jb2wocmVjMS5nbG9iYWwuREVHMSldCmNvbG5hbWVzKHJlYzEuZ2xvYmFsLkRFRylbM108LSJsb2cyRm9sZENoYW5nZS5tYXhfRkMiCmNvbG5hbWVzKHJlYzEuZ2xvYmFsLkRFRylbNF08LSJsb2cyRm9sZENoYW5nZS5tYXhfU0MiCgpgYGAKCgojIyBQbG90dGluZyBpbmRpdmlkdWFsIERFR3M6CiAgV2UgcGxvdCB2c3QtdHJhbnNmb3JtZWQgZ2Vub21lLWFsaWduaW5nIHNwYWNlciBjb3VudHMgZm9yIDYgZ2VuZXMgaW4gdGhlIGdudFIgcGF0aHdheSBmb3IgY2hvdyBhbmQgc3RhcmNoIGZlZCBtaWNlIG9uIGRheSA3LgogIApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CmdudFJfZ2VuZXM8LWMoJ2VkYScsJ2VkZCcsICdnbnRUJywgJ2tkZ1QnLCAnZ250VScsICdnbnRLJykKZGU8LXJlYzFkX2RheTdbcmVjMWRfZGF5NyREaWV0IT0nRmF0JyxdCmR0PC1yZWMxX2RheTd0ZltnbnRSX2dlbmVzLCByb3duYW1lcyhkZSldCnJlYzEuZ250Ui5wbG90LmRmPC1kYXRhLmZyYW1lKERpZXQ9ZGUkRGlldCwgdChkdCkpCnJlYzEuZ250Ui5wbG90LmRmPC1tZWx0KHJlYzEuZ250Ui5wbG90LmRmLCBpZC52YXJzID0gJ0RpZXQnKQpyZWMxLmdudFIucGxvdDwtZ2dwbG90KHJlYzEuZ250Ui5wbG90LmRmLCBhZXMoeT12YWx1ZSwgeD12YXJpYWJsZSwgZmlsbD1EaWV0LCBjb2xvcj0nYmxhY2snKSkrZ2VvbV9ib3hwbG90KHNpemU9MC4yNCwgb3V0bGllci5zaXplPTApK2dlb21fcG9pbnQoc2l6ZT0wLjQ4LCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuNzUpKSt0aGVtZV9wdWIreWxhYigiZ2VuZS1hbGlnbmluZyBzcGFjZXIgY291bnRzICh2c3QtdHJhbnNmb3JtZWQpIikreGxhYigiZ2VuZSIpK2dndGl0bGUoIlJlY29yZC1zZXEgY291bnRzIG9uIGRheSA3IGZvciBnbnRSIGdlbmUiKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldFtjKDEsMyldKSkrc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImJsYWNrIiksIGd1aWRlPSdub25lJykKcmVjMS5nbnRSLnBsb3QrcGxvdF9hbm5vdGF0aW9uKCkKCgpgYGAKIyMgSGVhdG1hcCBmb3IgUmVjb3JkLXNlcSBhbmQgUk5BLXNlcSBERUdzIG9uIGRheSA3CiAgV2UgcGxvdCBoZWF0bWFwcyBzaG93aW5nIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIHNhbXBsZXMgdXNpbmcgZGV0ZWN0ZWQgREUgZ2VuZXMgZm9yIGJvdGggUmVjb3JkLXNlcSBhbmQgUk5BLXNlcSBvbiBkYXkgNy4gCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CmNvbHM8LSBjb2xvclJhbXBQYWxldHRlKGMoImRvZGdlcmJsdWU0IiwgIndoaXRlIiwidmlvbGV0cmVkNCIpKSgyNTYpCnJpYm9zb21hbDwtYyhncmVwKCJycnMiLCByb3duYW1lcyhyZWMxLkRFRykpLCBncmVwKCJycmwiLCByb3duYW1lcyhyZWMxLkRFRykpKQppZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICByZWMxLkRFRzwtcmVjMS5ERUdbLXJpYm9zb21hbCxdCn0KZGhlYXRtYXA8LWFzLmRhdGEuZnJhbWUodChhcHBseShyZWMxX2RheTd0ZltyZWMxLkRFRyRnZW5lSUQsXSwgMSwgenNjb3Jlc3RhbmRhcmRpemUpKSkKaGVhdG1hcC5yZWMxLmRheTc8LXBoZWF0bWFwKGRoZWF0bWFwLCBhbm5vdGF0aW9uX2NvbCA9IHJlYzFkX2RheTdbLDEsIGRyb3A9RkFMU0VdLCBhbm5vdGF0aW9uX2NvbG9ycz1jb2xvdXJfY29kZSwgZm9udHNpemUgPSA1LCBmb250c2l6ZV9yb3cgPSA1LCBmb250c2l6ZV9jb2wgPSA1LCBjbHVzdGVyX3Jvd3MgPSBUUlVFLCB0cmVlaGVpZ2h0X3JvdyA9IDAsIGNsdXN0ZXJpbmdfZGlzdGFuY2VfY29scyA9ICJjYW5iZXJyYSIsIHRyZWVoZWlnaHRfY29sID0gNSxzaG93X2NvbG5hbWVzID0gRkFMU0UsIHNob3dfcm93bmFtZXMgPSBGQUxTRSwgY29sb3IgPSBjb2xzLGZvbnRzaXplX251bWJlcj01LCB3aWR0aD0yLjI4LCBoZWlnaHQ9Mi4yOCwgbWFpbj0nUmVjb3JkLXNlcSBERUdzIGRheSA3JykKcmlib3NvbWFsPC1jKGdyZXAoInJycyIsIHJvd25hbWVzKHJuYTEuREVHKSksIGdyZXAoInJybCIsIHJvd25hbWVzKHJuYTEuREVHKSkpCmlmKGxlbmd0aChyaWJvc29tYWwpPjApewogIHJuYTEuREVHPC1ybmExLkRFR1stcmlib3NvbWFsLF0KfQpjb2xzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJkb2RnZXJibHVlNCIsICJ3aGl0ZSIsInZpb2xldHJlZDQiKSkoMjU2KQpkaGVhdG1hcDwtYXMuZGF0YS5mcmFtZSh0KGFwcGx5KHJuYTFfdGZbcm5hMS5ERUckZ2VuZUlELF0sIDEsIHpzY29yZXN0YW5kYXJkaXplKSkpCmhlYXRtYXAucm5hMS5kYXk3PC1waGVhdG1hcChkaGVhdG1hcCwgYW5ub3RhdGlvbl9jb2wgPSBybmExZFssMSwgZHJvcD1GQUxTRV0sIGFubm90YXRpb25fY29sb3JzPWNvbG91cl9jb2RlLCBmb250c2l6ZSA9IDUsIGZvbnRzaXplX3JvdyA9IDUsIGZvbnRzaXplX2NvbCA9IDUsIGNsdXN0ZXJfcm93cyA9IFRSVUUsIHRyZWVoZWlnaHRfcm93ID0gMCwgdHJlZWhlaWdodF9jb2wgPSA1LCBzaG93X2NvbG5hbWVzID0gRkFMU0Usc2hvd19yb3duYW1lcyA9IEZBTFNFLCBjb2xvciA9IGNvbHMsZm9udHNpemVfbnVtYmVyPTUsIHdpZHRoPTIuMjgsIGhlaWdodD0yLjI4LCBtYWluPSdSTkEtc2VxIERFR3MgZGF5IDcnKQpgYGAKCiMjIFZvbGNhbm8gcGxvdHMgZm9yIFJlY29yZC1zZXEgREVHcwoKICBXZSBwZXJmb3JtIHBhaXJ3aXNlIERFIGFuYWx5c2lzIHVzaW5nIERFU2VxMiBhbmQgZWRnZVIgdG8gaWRlbnRpZnkgbG9nMkZDIGFuZCBwLWFkaiB2YWx1ZXMgZm9yIGVhY2ggZGlldCBwYWlyIG9uIGRheSA3LCBhbmQgcGxvdCB2b2xjYW5vZXMgKGxvZzJGQz4xLjUsIHBhZGo8MC4xKQoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi4yLCBmaWcud2lkdGg9M30KbGV2ZWxzPC1zb3J0KHVuaXF1ZShyZWMxZF9kYXk3WywxXSkpCnBhaXJ3aXNlLmNvbWJvPC1jb21ibihsZXZlbHMsIDIpCmNvbG9yLmNvbWJvPC1jb21ibihjb2xvdXJfY29kZSREaWV0LCAyKQpyZWMxLmRlLnZhbHM8LWxpc3QoKQpyZWMxLmVkLnZhbHM8LWxpc3QoKQp2b2wucGxvdHM8LWxpc3QoKQpERUc8LWxpc3QoKQpmb3IoaSBpbiAxOmRpbShwYWlyd2lzZS5jb21ibylbMl0pewogIGRzPC1yZWMxZF9kYXk3W3doaWNoKHJlYzFkX2RheTdbLDFdJWluJXBhaXJ3aXNlLmNvbWJvWyxpXSksXQogIGRzJERpZXQ8LWFzLmNoYXJhY3RlcihkcyREaWV0KQogIGR0PC1yZWMxX2RheTdbLHJvd25hbWVzKGRzKV0KICBkdGY8LXJlY29SZHNlcS50cmFuc2Zvcm0oZHQsZHMpCiAgcmVjMS5kZS52YWxzW1tpXV0gPC0gcmVjb1Jkc2VxLkRFKGR0LCBkcywgdG9vbCA9ICdERVNlcTInKQogIHJlYzEuZWQudmFsc1tbaV1dIDwtIHJlY29SZHNlcS5ERShkdCwgZHMsIHRvb2wgPSAnZWRnZVInKQogIHJvd25hbWVzKHJlYzEuZGUudmFsc1tbaV1dKSA8LSByZWMxLmRlLnZhbHNbW2ldXSRnZW5lSUQKICByb3duYW1lcyhyZWMxLmVkLnZhbHNbW2ldXSkgPC0gcmVjMS5lZC52YWxzW1tpXV0kZ2VuZUlECiAgZGVzZXEuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMS5kZS52YWxzW1tpXV0sIHAgPSAwLjEpCiAgZWRnZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMS5lZC52YWxzW1tpXV0sIHAgPSAwLjEpCiAgREVHW1tpXV08LWRhdGEuZnJhbWUocm93Lm5hbWVzPWludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLGdlbmVJRD1pbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSxsb2cyRm9sZGNoYW5nZT1yZWMxLmRlLnZhbHNbW2ldXVtpbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSwgNF0sIHBhZGo9cmVjMS5kZS52YWxzW1tpXV1baW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksIDddKQogIHJpYm9zb21hbDwtYyhncmVwKCJycnMiLCByb3duYW1lcyhERUdbW2ldXSkpLCBncmVwKCJycmwiLCByb3duYW1lcyhERUdbW2ldXSkpKQogIGlmKGxlbmd0aChyaWJvc29tYWwpPjApewogICAgICBERUdbW2ldXTwtREVHW1tpXV1bLXJpYm9zb21hbCxdCiAgfQogIERFR1tbaV1dJGdlbmVJRDwtYXMuY2hhcmFjdGVyKERFR1tbaV1dJGdlbmVJRCkKICByZWMxLmRlLnZhbHNbW2ldXTwtcmVjMS5kZS52YWxzW1tpXV1bY29tcGxldGUuY2FzZXMocmVjMS5kZS52YWxzW1tpXV0pLF0KICByZWMxLmRlLnZhbHNbW2ldXSRHcm91cDwtJ05vbmUnCiAgcmVjMS5kZS52YWxzW1tpXV0kR3JvdXBbIHdoaWNoKHJlYzEuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlPjEuNSZyZWMxLmRlLnZhbHNbW2ldXSRwYWRqPDAuMSldPC1wYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIGFzLmNoYXJhY3Rlcihzb3J0KHVuaXF1ZShkcyREaWV0KSlbMl0pKQogIHJlYzEuZGUudmFsc1tbaV1dJEdyb3VwWyB3aGljaChyZWMxLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZTwoLTEuNSkmcmVjMS5kZS52YWxzW1tpXV0kcGFkajwwLjEpXTwtcGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBzb3J0KHVuaXF1ZShkcyREaWV0KSlbMV0pCiByZWMxLmRlLnZhbHNbW2ldXSRHcm91cDwtZmFjdG9yKHJlYzEuZGUudmFsc1tbaV1dJEdyb3VwLCBsZXZlbHMgPSBjKHBhc3RlMCgidXByZWd1bGF0ZWQuaW4uIiwgYXMuY2hhcmFjdGVyKHNvcnQodW5pcXVlKGRzJERpZXQpKVsxXSkpLCBwYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIHNvcnQodW5pcXVlKGRzJERpZXQpKVsyXSksICdOb25lJyApKQogIHJlYzEuZGUudmFsc1tbaV1dJGxhYmVsPC1GQUxTRQogIG0xPC1yZWMxLmRlLnZhbHNbW2ldXVtyZWMxLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZT4xLjUsICdnZW5lSUQnXVsxOjEwXQogIG0yPC1yZWMxLmRlLnZhbHNbW2ldXVtyZWMxLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZTwoLTEuNSksICdnZW5lSUQnXVsxOjEwXQogIG08LXdoaWNoKHJlYzEuZGUudmFsc1tbaV1dJGdlbmVJRCVpbiV1bmlvbihtMSxtMikpCiAgZm9yKGogaW4gbSl7CiAgICBpZihhYnMocmVjMS5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2Vbal0pPjEuNSZyZWMxLmRlLnZhbHNbW2ldXSRwYWRqW2pdPDAuMSl7CiAgICAgIHJlYzEuZGUudmFsc1tbaV1dJGxhYmVsW2pdPC1UUlVFCiAgICB9CiAgfQogIHZvbC5wbG90c1tbaV1dPC1nZ3Bsb3QocmVjMS5kZS52YWxzW1tpXV0sIGFlcyggeD1sb2cyRm9sZENoYW5nZSwgeT0oLWxvZzEwKHBhZGopKSwgY29sb3I9R3JvdXApKStzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoY29sb3IuY29tYm9bLGldLCAnZ3JheTcwJykpK2dlb21fcG9pbnQoc2l6ZT0wLjI0KStnZW9tX3RleHRfcmVwZWwoZGF0YSA9IHJlYzEuZGUudmFsc1tbaV1dW3doaWNoKHJlYzEuZGUudmFsc1tbaV1dJGxhYmVsKSxdLCBhZXMoIHg9bG9nMkZvbGRDaGFuZ2UsIHk9KC1sb2cxMChwYWRqKSksIGxhYmVsPWdlbmVJRCksIHNpemU9MS43Niwgc2hvdy5sZWdlbmQ9RkFMU0UpK3RoZW1lX3B1YitnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAxLjUsIHNpemU9MC4yNCkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gLTEuNSwgc2l6ZT0wLjI0KStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLCBzaXplPTAuMjQpK3hsYWIoImxvZzIgZm9sZCBjaGFuZ2UiKSt5bGFiKCItbG9nMTAgcC1hZGp1c3RlZCB2YWx1ZSIpK2d1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MS41KSkpCn0KCnZvbC5wbG90c1tbMV1dICsgdm9sLnBsb3RzW1syXV0gK3ZvbC5wbG90c1tbM11dICsgcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSAnQScpK3Bsb3RfbGF5b3V0KG5jb2wgPSAyKQoKYGBgCgojIyBDbHVzdGVyaW5nIG9uIGZpbmFsIGRheSBvZiBleHBlcmltZW50CiAgV2Ugd2FudCB0byBjaGVjayB3aGV0aGVyIGluZm9ybWF0aW9uIGFib3V0IGRpZXQgZ3JvdXBzIHByaW9yIHRvIHN3aXRjaCBjYW4gYmUgcmV0cmlldmVkIGF0IGRheSAxNCAtIGkuZSA3IGRheXMgYWZ0ZXIgdGhlIHN3aXRjaC4gRm9yIHRoaXMsIHdlIHVzZSBkaWV0LXNpZ25hdHVyZSBnZW5lcyBpZGVudGlmaWVkIGJlZm9yZSB0aGUgc3dpdGNoIHRvIGhpZXJhcmNoaWNhbGx5IGNsdXN0ZXIgdGhlIGdyb3Vwcy4gRGlldC1zaWduYXR1cmUgZ2VuZXMgYXJlIGRlZmluZWQgaGVyZSBhcyB0aGUgdG9wIDEwIGdlbmVzIGJ5IG51bWJlciBvZiBkYXlzIChSZWNvcmQtc2VxKSBvciBwLWFkaiB2YWx1ZSAoUk5BLXNlcSkgZGV0ZWN0ZWQgYXMgZW5yaWNoZWQgKGxvZzJGQyA+IDIuNSkgcHJpb3IgdG8gdGhlIHN3aXRjaC4gV2UgY2FuIHBlcmZlY3RseSBjbGFzc2lmeSBncm91cHMgdXNpbmcgUmVjb3JkLXNlcSBkYXRhLCB3aGlsZSBmb3IgUk5BLXNlcSwgdGhlIGdyb3VwcyBjb252ZXJnZS4gCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CnJpYm9zb21hbDwtYyhncmVwKCJycnMiLCByZWMxLmdsb2JhbC5ERUckZ2VuZUlEKSwgZ3JlcCgicnJsIiwgcmVjMS5nbG9iYWwuREVHJGdlbmVJRCkpCmlmKGxlbmd0aChyaWJvc29tYWwpPjApewogIHJlYzEuZ2xvYmFsLkRFRzwtcmVjMS5nbG9iYWwuREVHWy1yaWJvc29tYWwsXQp9CmdlbmVTaG9ydExpc3Q8LXVuaXF1ZShjKHJlYzEuZ2xvYmFsLkRFR1t3aGljaChyZWMxLmdsb2JhbC5ERUckbG9nMkZvbGRDaGFuZ2UubWF4X0ZDPjIuNSksIDFdWzE6MTBdLCByZWMxLmdsb2JhbC5ERUdbd2hpY2gocmVjMS5nbG9iYWwuREVHJGxvZzJGb2xkQ2hhbmdlLm1heF9TQz4yLjUpLCAxXVsxOjEwXSxyZWMxLmdsb2JhbC5ERUdbd2hpY2gocm93TWVhbnMocmVjMS5nbG9iYWwuREVHWywzOjRdKTwoLTIuNSkpLCAxXVsxOjEwXSkpCmNvbHM8LSBjb2xvclJhbXBQYWxldHRlKGMoImRvZGdlcmJsdWU0IiwgIndoaXRlIiwidmlvbGV0cmVkNCIpKSgyNTYpCmRoZWF0bWFwPC1hcy5kYXRhLmZyYW1lKHQoYXBwbHkocmVjMV9kYXkxNHRmW2dlbmVTaG9ydExpc3QsXSwgMSwgenNjb3Jlc3RhbmRhcmRpemUpKSkKaGVhdG1hcC5yZWMxPC1waGVhdG1hcChkaGVhdG1hcCwgYW5ub3RhdGlvbl9jb2wgPSByZWMxZF9kYXkxNFssMSwgZHJvcD1GQUxTRV0sIGFubm90YXRpb25fY29sb3JzPWNvbG91cl9jb2RlLCB0cmVlaGVpZ2h0X3Jvdz0wLCB0cmVlaGVpZ2h0X2NvbD01LCBmb250c2l6ZSA9IDUsIGZvbnRzaXplX3JvdyA9IDUsIGZvbnRzaXplX2NvbCA9IDUsIGNsdXN0ZXJfcm93cyA9IEZBTFNFLCBzaG93X2NvbG5hbWVzID0gRkFMU0UsIGNvbG9yID0gY29scyxmb250c2l6ZV9udW1iZXI9NSwgbWFpbj0nSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgb2YgUmVjb3JkLXNlcSBkYXRhIG9uIGRheSAxNCBiYXNlZCBvbiBERUdTIGRldGVjdGVkIG9uIGRheSA3JykKaGVhdG1hcC5nZW5lbGlzdDwtcmVjMV9kYXkxNHRmW2dlbmVTaG9ydExpc3QsXQpjb2xuYW1lcyhoZWF0bWFwLmdlbmVsaXN0KTwtcmVjMWRfZGF5MTQkRGlldAoKCmNvbHM8LSBjb2xvclJhbXBQYWxldHRlKGMoImRvZGdlcmJsdWU0IiwgIndoaXRlIiwidmlvbGV0cmVkNCIpKSgyNTYpCmdlbmVTaG9ydExpc3Q8LXVuaXF1ZShjKHJuYTEuREVHW3doaWNoKHJuYTEuREVHJGxvZzJGb2xkQ2hhbmdlLkZhdF92c19DaG93PjIuNSksIDFdWzE6MTJdLCBybmExLkRFR1t3aGljaChybmExLkRFRyRsb2cyRm9sZENoYW5nZS5TdGFyY2hfdnNfQ2hvdz4yLjUpLCAxXVsxOjEyXSxybmExLkRFR1t3aGljaChyb3dNZWFucyhybmExLkRFR1ssMzo0XSk8KC0yLjUpKSwgMV1bMToxMl0pKQpkaGVhdG1hcDwtYXMuZGF0YS5mcmFtZSh0KGFwcGx5KHJuYTFfZGF5MTR0ZltnZW5lU2hvcnRMaXN0LF0sIDEsIHpzY29yZXN0YW5kYXJkaXplKSkpCmRoZWF0bWFwPC1kaGVhdG1hcFtjb21wbGV0ZS5jYXNlcyhkaGVhdG1hcCksXQpoZWF0bWFwLnJuYTE8LXBoZWF0bWFwKGRoZWF0bWFwLCBhbm5vdGF0aW9uX2NvbCA9IHJuYTFkX2RheTE0WywxLCBkcm9wPUZBTFNFXSwgYW5ub3RhdGlvbl9jb2xvcnM9Y29sb3VyX2NvZGUsIHRyZWVoZWlnaHRfcm93PTAsICB0cmVlaGVpZ2h0X2NvbD01LCBmb250c2l6ZSA9IDUsIGZvbnRzaXplX3JvdyA9IDUsIGZvbnRzaXplX2NvbCA9IDUsIGNsdXN0ZXJfcm93cyA9IEZBTFNFLCBzaG93X2NvbG5hbWVzID0gRkFMU0UsIGNvbG9yID0gY29scyxmb250c2l6ZV9udW1iZXI9NSwgbWFpbj0nSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgb2YgUk5BLXNlcSBkYXRhIG9uIGRheSAxNCBiYXNlZCBvbiBERUdTIGRldGVjdGVkIG9uIGRheSA3JykKCmBgYAoKIyBUcmFuc2llbnQgRGlldCAyCgogIFdlIG5vdyBhbmFseXplIGRhdGEgZm9yIHRoZSBleHRlbmRlZCB0cmFuc2llbnQgZGlldCBleHBlcmltZW50IHdpdGggMjAgZGF5cy4KCiMjIEltcG9ydGluZyBhbmQgcHJlLXByb2Nlc3NpbmcgZGF0YSBmb3IgdHJhbnNpZW50IGRpZXQgMgogIFdlIGltcG9ydCB0aGUgZGF0YSBtYXRyaWNlcywgZmlsdGVyIHRoZW0gZm9yIGxvd2x5IGV4cHJlc3NlZCBnZW5lcyBhcyB3ZWxsIGFzIG91dGxpZXIgc2FtcGxlcyB3aXRoIGxvdyBjdW11bGF0aXZlIGNvdW50cywgYW5kIHVzZSB2c3QgZnJvbSBERVNlcTIgdG8gbm9ybWFsaXplIGFuZCB0cmFuc2Zvcm0gdGhlIGRhdGEuIFdlIGFsc28gZXhjbHVkZSBkYXkxIGZyb20gdGhlIGFuYWx5c2lzIHNpbmNlIHdlIGhhdmUgZW1wZXJpY2FsbHkgb2JzZXJ2ZWQgdGhhdCB0aGUgZGF0YSBhcmUgbm9pc3kgZm9yIHRoZSBmaXJzdCBkYXkgb2YgY29sb25pemF0aW9uLiBXZSB1c2UgdGhlIHNhbWUgdGhyZXNob2xkcyBhcyB0aGUgcHJldmlvdXMgZXhwZXJpbWVudCBmb3IgY29uc2lzdGVuY3kuCiAgCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi4yLCBmaWcud2lkdGg9M30KcmVjMjwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL3RyYW5zaWVudERpZXQyX1JlY29yZHNlcV9nZW5vbWVhbGlnbmluZy50eHQiLCBoZWFkZXIgPSBUUlVFKSkKcmVjMmQ8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS90cmFuc2llbnREaWV0Ml9SZWNvcmRzZXFfbWV0YWRhdGEudHh0IiwgaGVhZGVyID0gVFJVRSkpCkRFTGlzdDwtcmVjb1Jkc2VxLnByZXByb2Nlc3MocmVjMiwgcmVjMmQsIG1pbkNvdW50c1BlclNhbXBsZSA9IDEwMDAwKQpyZWMyPC1ERUxpc3RbWzFdXQpyZWMyZDwtREVMaXN0W1syXV0KcmVjMmQ8LXJlYzJkW3JlYzJkJERheT4xLF0KcmVjMjwtcmVjMlsscm93bmFtZXMocmVjMmQpXQpyZWMyX3RmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJlYzIsIHJlYzJkKQpybmEyPC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvdHJhbnNpZW50RGlldDJfUk5Bc2VxX2dlbm9tZWFsaWduaW5nLnR4dCIsIGhlYWRlciA9IFRSVUUpKQpybmEyZDwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL3RyYW5zaWVudERpZXQyX1JOQXNlcV9tZXRhZGF0YS50eHQiLCBoZWFkZXIgPSBUUlVFKSkKcm5hMmQ8LXJuYTJkWywxOjNdCkRFTGlzdDwtcmVjb1Jkc2VxLnByZXByb2Nlc3Mocm5hMiwgcm5hMmQsIG1pbkNvdW50c1BlclNhbXBsZSA9IDEwMDAwMCkKcm5hMjwtREVMaXN0W1sxXV0Kcm5hMmQ8LURFTGlzdFtbMl1dCnJuYTJfdGY8LXJlY29SZHNlcS50cmFuc2Zvcm0ocm5hMiwgcm5hMmQpCnJuYWRheXM8LXVuaXF1ZShybmEyZCREYXkpCmBgYAoKIyMgRGF0YSBleHBsb3JhdGlvbgogIFdlIHVzZSBQcmluY2lwYWwgQ29tcG9uZW50IGFuYWx5c2lzIGFuZCBVTUFQIGZvciBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gYW5kIGV4cGxvcmluZyBjbHVzdGVycyBpbiBhbiB1bnN1cGVydmlzZWQgZmFzaGlvbiBpbiBvdXIgZGF0YS4gIFdlIGZpcnN0IGdlbmVyYXRlIHRoZXNlIGZvciB0aGUgZW50aXJlIGRhdGFzZXQgZnJvbSBSZWNvcmQtc2VxOgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQpyZWMyc2RzIDwtIHJvd1Nkcyhhcy5tYXRyaXgocmVjMl90ZikpCm8gPC0gb3JkZXIocmVjMnNkcywgZGVjcmVhc2luZyA9IFRSVUUpCnJlYzJQQ0E8LXByY29tcCh0KHJlYzJfdGZbb1sxOjUwMF0sXSkpCnBjYV9zdGF0PC1zdW1tYXJ5KHJlYzJQQ0EpCnJlYzIucGNhX3ZhcmlhbmNlPC1wY2Ffc3RhdCRpbXBvcnRhbmNlWzIsXQpyZWMyUENBPC1hcy5kYXRhLmZyYW1lKHJlYzJQQ0EkeCkKcmVjMlBDQSREaWV0PC1yZWMyZCREaWV0CnJlYzJQQ0EkRGF5PC1mYWN0b3IocmVjMmQkRGF5LCBsZXZlbHMgPSAxOjIwKQpyZWMyUENBcGxvdDwtZ2dwbG90KHJlYzJQQ0EsIGFlcyh4PVBDMSwgeT1QQzIsIGZpbGw9RGlldCwgIHNpemU9RGF5KSkrZ2VvbV9wb2ludChwY2g9MjEsIGNvbG91cj0nIzAwMDAwMCcsIHN0cm9rZT0wLjI1KSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9kaXNjcmV0ZShyYW5nZT1jKDEsMi41KSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldCkpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3lsYWIocGFzdGUwKCJQQzIgKCIsIGFzLmNoYXJhY3RlcihyZWMyLnBjYV92YXJpYW5jZVsyXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK3hsYWIocGFzdGUwKCJQQzEgKCIsIGFzLmNoYXJhY3RlcihyZWMyLnBjYV92YXJpYW5jZVsxXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK2dndGl0bGUoIiBQQ0EgcGxvdCBvZiBSZWNvcmQtc2VxIGRhdGEiKQoKcmVjMlVNQVA8LXVtYXAocmVjMlBDQVssMToobmNvbChyZWMyUENBKS0yKV0sIGN1c3RvbS5jb25maWcpCnJlYzJVTUFQPC1hcy5kYXRhLmZyYW1lKHJlYzJVTUFQJGxheW91dCkKcmVjMlVNQVAkRGF5PC1mYWN0b3IocmVjMmQkRGF5LCBsZXZlbHMgPSAxOjIwKQpyZWMyVU1BUCREaWV0PC1yZWMyZCREaWV0CmNvbG5hbWVzKHJlYzJVTUFQKVsxOjJdPC1jKCdVTUFQMScsJ1VNQVAyJykKcmVjMlVNQVBwbG90PC1nZ3Bsb3QocmVjMlVNQVAsIGFlcyh4PVVNQVAxLCB5PVVNQVAyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygxLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KSsgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTIpKSkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZ3RpdGxlKCJVTUFQIHBsb3QgZm9yIFJlY29yZC1zZXEgZGF0YSAoYWxsIGRheXMpIikKCnJlYzJQQ0FwbG90K3JlYzJVTUFQcGxvdCtwbG90X2Fubm90YXRpb24odGFnX2xldmVscyA9ICdBJykKCmBgYAoKCiAgRm9yIGEgY29tcGFyYXNpb24gYmV0d2VlbiBSZWNvcmQtc2VxIGFuZCBSTkEtc2VxLCB3ZSBleGNsdWRlIHRoZSBkYXlzIGluIFJlY29yZC1zZXEgdGhhdCBkb24ndCBoYXZlIGNvcnJlc3BvbmRpbmcgUk5BLXNlcSBkYXRhLiBXZSBmaXJzdCBjaGVjayBpZiB0aGUgZGlldCBncm91cHMgY2FuIGJlIGNsYXNzaWZpZWQgdXNpbmcgUENBIG9uIGRheSA3ICh0aGUgbGFzdCBkYXkgd2hlbiB0aGUgbWljZSBhcmUgZmVkIGRpZmZlcmVudCBkaWV0cywgYmVmb3JlIHN3aXRjaGluZyBhbGwgbWljZSB0byBhICdDaG93JyBkaWV0KQoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQoKcmVjMmRfZGF5NzwtcmVjMmRbcmVjMmQkRGF5PT03LF0KcmVjMl9kYXk3PC1yZWMyWywgcm93bmFtZXMocmVjMmRfZGF5NyldCnJlYzJfZGF5N3RmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJlYzJfZGF5NywgcmVjMmRfZGF5NykKcmVjMl9kYXk3X3NkcyA8LSByb3dTZHMoYXMubWF0cml4KHJlYzJfZGF5N3RmKSkKbyA8LSBvcmRlcihyZWMyX2RheTdfc2RzLCBkZWNyZWFzaW5nID0gVFJVRSkKcmVjMl9kYXk3UENBPC1wcmNvbXAodChyZWMyX2RheTd0ZltvWzE6NTAwXSxdKSkKcGNhX3N0YXQ8LXN1bW1hcnkocmVjMl9kYXk3UENBKQpwY2FfdmFyaWFuY2U8LXBjYV9zdGF0JGltcG9ydGFuY2VbMixdCnJlYzJfZGF5N1BDQTwtYXMuZGF0YS5mcmFtZShyZWMyX2RheTdQQ0EkeCkKcmVjMl9kYXk3UENBJERpZXQ8LXJlYzJkX2RheTckRGlldApyZWMyX2RheTdQQ0EkRGF5PC1mYWN0b3IocmVjMmRfZGF5NyREYXksIGxldmVscyA9IGMoNykpCnJlYzJfZGF5N1BDQXBsb3Q8LWdncGxvdChyZWMyX2RheTdQQ0EsIGFlcyh4PVBDMSwgeT1QQzIsIGZpbGw9RGlldCwgIHNpemU9RGF5KSkrZ2VvbV9wb2ludChwY2g9MjEsIGNvbG91cj0nIzAwMDAwMCcsIHN0cm9rZT0wLjI1KSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9kaXNjcmV0ZShyYW5nZT1jKDIsMi41KSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldCkpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3lsYWIocGFzdGUwKCJQQzIgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMl0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKSt4bGFiKHBhc3RlMCgiUEMxICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzFdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkrZ2d0aXRsZSgiIFBDQSBwbG90IG9mIFJlY29yZC1zZXEgZGF0YSBvbiBkYXkgNyAiKQoKcm5hMmRfZGF5Nzwtcm5hMmRbcm5hMmQkRGF5PT03LF0Kcm5hMl9kYXk3PC1ybmEyWywgcm93bmFtZXMocm5hMmRfZGF5NyldCnJuYTJfZGF5N3RmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJuYTJfZGF5Nywgcm5hMmRfZGF5NykKcm5hMl9kYXk3X3NkcyA8LSByb3dTZHMoYXMubWF0cml4KHJuYTJfZGF5N3RmKSkKbyA8LSBvcmRlcihybmEyX2RheTdfc2RzLCBkZWNyZWFzaW5nID0gVFJVRSkKcm5hMl9kYXk3UENBPC1wcmNvbXAodChybmEyX2RheTd0ZltvWzE6NTAwXSxdKSkKcGNhX3N0YXQ8LXN1bW1hcnkocm5hMl9kYXk3UENBKQpwY2FfdmFyaWFuY2U8LXBjYV9zdGF0JGltcG9ydGFuY2VbMixdCnJuYTJfZGF5N1BDQTwtYXMuZGF0YS5mcmFtZShybmEyX2RheTdQQ0EkeCkKcm5hMl9kYXk3UENBJERpZXQ8LXJuYTJkX2RheTckRGlldApybmEyX2RheTdQQ0EkRGF5PC1mYWN0b3Iocm5hMmRfZGF5NyREYXksIGxldmVscyA9IGMoNykpCnJuYTJfZGF5N1BDQXBsb3Q8LWdncGxvdChybmEyX2RheTdQQ0EsIGFlcyh4PVBDMSwgeT1QQzIsIGZpbGw9RGlldCwgIHNpemU9RGF5KSkrZ2VvbV9wb2ludChwY2g9MjEsIGNvbG91cj0nIzAwMDAwMCcsIHN0cm9rZT0wLjI1KSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9kaXNjcmV0ZShyYW5nZT1jKDIsMi41KSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldCkpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3lsYWIocGFzdGUwKCJQQzIgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMl0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKSt4bGFiKHBhc3RlMCgiUEMxICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzFdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkrZ2d0aXRsZSgiIFBDQSBwbG90IG9mIFJOQS1zZXEgZGF0YSBvbiBkYXkgNyAiKQoKCnJlYzJfZGF5N1BDQXBsb3Qrcm5hMl9kYXk3UENBcGxvdCtwbG90X2Fubm90YXRpb24odGFnX2xldmVscyA9ICdBJykKCmBgYAoKICBXZSB0aGVuIGNvbXBhcmUgdGhlIHRlbXBvcmFsIHRyYWplY3RvcmllcyBvZiBSZWNvcmQtc2VxIGFuZCBSTkEtc2VxIHVzaW5nIFVNQVBzLiBSZWNvcmQtc2VxIGRhdGEgcmV0YWlucyBpbmZvcm1hdGlvbiBhYm91dCBwcmlvciBkaWV0IGdyb3VwcyB0aWxsIHRoZSBmaW5hbCBkYXksIGFuZCBjbHVzdGVycyBzdHJvbmdseSBiYXNlZCBvbiBncm91cDsgd2hlcmVhcyBSTkEtc2VxIGRhdGEgaGFzIGEgcHJvbm91bmNlZCB0ZW1wb3JhbCBjaGFuZ2UsIGFuZCBpbml0aWFsIGNsdXN0ZXJzIGZvciBkaWZmZXJlbnQgZGlldCBncm91cHMgcXVpY2tseSBjb252ZXJnZS4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi4yLCBmaWcud2lkdGg9M30KcmVjMmRfcm5hPC1yZWMyZFt3aGljaChyZWMyZCREYXklaW4lcm5hZGF5cyksXQpyZWMyX3JuYTwtcmVjMlssIHJvd25hbWVzKHJlYzJkX3JuYSldCnJlYzJfcm5hdGY8LXJlY29SZHNlcS50cmFuc2Zvcm0ocmVjMl9ybmEsIHJlYzJkX3JuYSkKcmVjMnJuYXNkcyA8LSByb3dTZHMoYXMubWF0cml4KHJlYzJfcm5hdGYpKQpvIDwtIG9yZGVyKHJlYzJybmFzZHMsIGRlY3JlYXNpbmcgPSBUUlVFKQpyZWMycm5hUENBPC1wcmNvbXAodChyZWMyX3JuYXRmW29bMTo1MDBdLF0pKQpwY2Ffc3RhdDwtc3VtbWFyeShyZWMycm5hUENBKQpyZWMycm5hLnBjYV92YXJpYW5jZTwtcGNhX3N0YXQkaW1wb3J0YW5jZVsyLF0KcmVjMnJuYVBDQTwtYXMuZGF0YS5mcmFtZShyZWMycm5hUENBJHgpCnJlYzJybmFQQ0EkRGlldDwtcmVjMmRfcm5hJERpZXQKcmVjMnJuYVBDQSREYXk8LWZhY3RvcihyZWMyZF9ybmEkRGF5LCBsZXZlbHMgPSBybmFkYXlzKQpyZWMycm5hVU1BUDwtdW1hcChyZWMycm5hUENBWywxOihuY29sKHJlYzJybmFQQ0EpLTIpXSwgY3VzdG9tLmNvbmZpZykKcmVjMnJuYVVNQVA8LWFzLmRhdGEuZnJhbWUocmVjMnJuYVVNQVAkbGF5b3V0KQpyZWMycm5hVU1BUCREYXk8LWZhY3RvcihyZWMyZF9ybmEkRGF5LCBsZXZlbHMgPSBybmFkYXlzKQpyZWMycm5hVU1BUCREaWV0PC1yZWMyZF9ybmEkRGlldApjb2xuYW1lcyhyZWMycm5hVU1BUClbMToyXTwtYygnVU1BUDEnLCdVTUFQMicpCnJlYzJybmFVTUFQcGxvdDwtZ2dwbG90KHJlYzJybmFVTUFQLCBhZXMoeD1VTUFQMSwgeT1VTUFQMiwgZmlsbD1EaWV0LCAgc2l6ZT1EYXkpKStnZW9tX3BvaW50KHBjaD0yMSwgY29sb3VyPScjMDAwMDAwJywgc3Ryb2tlPTAuMjUpK3RoZW1lX3B1YitzY2FsZV9zaXplX2Rpc2NyZXRlKHJhbmdlPWMoMSwyLjUpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0KSkrZ2d0aXRsZSgiVU1BUCBwbG90IGZvciBSZWNvcmQtc2VxIGRhdGEiKQoKcm5hMnNkcyA8LSByb3dTZHMoYXMubWF0cml4KHJuYTJfdGYpKQpvIDwtIG9yZGVyKHJuYTJzZHMsIGRlY3JlYXNpbmcgPSBUUlVFKQpybmEyUENBPC1wcmNvbXAodChybmEyX3RmW29bMTo1MDBdLF0pKQpwY2Ffc3RhdDwtc3VtbWFyeShybmEyUENBKQpybmEyLnBjYV92YXJpYW5jZTwtcGNhX3N0YXQkaW1wb3J0YW5jZVsyLF0Kcm5hMlBDQTwtYXMuZGF0YS5mcmFtZShybmEyUENBJHgpCnJuYTJQQ0EkRGlldDwtcm5hMmQkRGlldApybmEyUENBJERheTwtZmFjdG9yKHJuYTJkJERheSwgbGV2ZWxzID0gcm5hZGF5cykKcm5hMlVNQVA8LXVtYXAocm5hMlBDQVssMToobmNvbChybmEyUENBKS0yKV0sIGN1c3RvbS5jb25maWcpCnJuYTJVTUFQPC1hcy5kYXRhLmZyYW1lKHJuYTJVTUFQJGxheW91dCkKcm5hMlVNQVAkRGF5PC1mYWN0b3Iocm5hMmQkRGF5LCBsZXZlbHMgPSBybmFkYXlzKQpybmEyVU1BUCREaWV0PC1ybmEyZCREaWV0CmNvbG5hbWVzKHJuYTJVTUFQKVsxOjJdPC1jKCdVTUFQMScsJ1VNQVAyJykKcm5hMlVNQVBwbG90PC1nZ3Bsb3Qocm5hMlVNQVAsIGFlcyh4PVVNQVAxLCB5PVVNQVAyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygxLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KSsgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTIpKSkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZ3RpdGxlKCJVTUFQIHBsb3QgZm9yIFJOQS1zZXEgZGF0YSIpCnJlYzJybmFVTUFQcGxvdCtybmEyVU1BUHBsb3QrcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSAnQScpCgpgYGAKCgojIyBEaXNjb3Zlcnkgb2YgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIChERUdzKToKICBXZSBpZGVudGlmeSBERUdzIGZvciBkYXkgNyAoc2luY2UgdGhpcyBpcyB0aGUgZmluYWwgZGF5IHdoZW4gdGhlIG1pY2UgYXJlIGZlZCBzZXBhcmF0ZSBkaWV0cywgYW5kIFJOQS1zZXEgaXMgYWxzbyBhdmFpbGFibGUgZm9yIHRoaXMgZGF5KS4gV2UgZGVmaW5lIERFR3MgYXMgZ2VuZXMgaWRlbnRpZmllZCB0byBiZSBzaWduaWZpY2FudGx5IGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCB1c2luZyBhIHRocmVzaG9sZCAocGFkaiA8IDAuMDUpIGJ5IGJvdGggREVTZXEyIGFuZCBlZGdlUiAobXVsdGlwbGUgdGVzdGluZywgc2luY2Ugd2UgaGF2ZSAzIGdyb3VwcykuICAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJlYzIuZGVzZXE8LXJlY29SZHNlcS5ERShyZWMyX2RheTcsIHJlYzJkX2RheTcsIHRvb2w9J0RFU2VxMicpCnJlYzIuZWRnZXI8LXJlY29SZHNlcS5ERShyZWMyX2RheTcsIHJlYzJkX2RheTcsIHRvb2w9J2VkZ2VSJykKcmVjMi5kZXNlcS5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMyLmRlc2VxLCBwID0gMC4wNSkKcmVjMi5lZGdlci5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMyLmVkZ2VyLCBwID0gMC4wNSkKcmVjMi5ERUc8LXJlYzIuZGVzZXFbaW50ZXJzZWN0KHJlYzIuZGVzZXEuZ2VuZXMsIHJlYzIuZWRnZXIuZ2VuZXMpLCBjKDEsNywgd2hpY2goZ3JlcGwoJ2xvZzJGb2xkQ2hhbmdlJywgY29sbmFtZXMocmVjMi5kZXNlcSkpKSldCnJlYzIuREVHJGdlbmVJRDwtYXMuY2hhcmFjdGVyKHJlYzIuREVHJGdlbmVJRCkKcmVjMi5ERUc8LXJlYzIuREVHW29yZGVyKHJlYzIuREVHJHBhZGopLF0Kcmlib3NvbWFsPC1jKGdyZXAoInJycyIsIHJvd25hbWVzKHJlYzIuREVHKSksIGdyZXAoInJybCIsIHJvd25hbWVzKHJlYzIuREVHKSkpCmlmKGxlbmd0aChyaWJvc29tYWwpPjApewogIHJlYzIuREVHPC1yZWMyLkRFR1stcmlib3NvbWFsLF0KfQpybmEyLmRlc2VxPC1yZWNvUmRzZXEuREUocm5hMl9kYXk3LCBybmEyZF9kYXk3LCB0b29sPSdERVNlcTInKQpybmEyLmVkZ2VyPC1yZWNvUmRzZXEuREUocm5hMl9kYXk3LCBybmEyZF9kYXk3LCB0b29sPSdlZGdlUicpCnJuYTIuZGVzZXEuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocm5hMi5kZXNlcSwgcCA9IDAuMDUpCnJuYTIuZWRnZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocm5hMi5lZGdlciwgcCA9IDAuMDUpCnJuYTIuREVHPC1ybmEyLmRlc2VxW2ludGVyc2VjdChybmEyLmRlc2VxLmdlbmVzLCBybmEyLmVkZ2VyLmdlbmVzKSwgYygxLDcsIHdoaWNoKGdyZXBsKCdsb2cyRm9sZENoYW5nZScsIGNvbG5hbWVzKHJuYTIuZGVzZXEpKSkpXQpybmEyLkRFRyRnZW5lSUQ8LWFzLmNoYXJhY3RlcihybmEyLkRFRyRnZW5lSUQpCnJuYTIuREVHPC1ybmEyLkRFR1tvcmRlcihybmEyLkRFRyRwYWRqKSxdCnJpYm9zb21hbDwtYyhncmVwKCJycnMiLCByb3duYW1lcyhybmEyLkRFRykpLCBncmVwKCJycmwiLCByb3duYW1lcyhybmEyLkRFRykpKQppZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICBybmEyLkRFRzwtcm5hMi5ERUdbLXJpYm9zb21hbCxdCn0KcmVjMi5ub3ZlbDwtcmVjMi5ERUdbLXdoaWNoKHJlYzIuREVHJGdlbmVJRCVpbiVybmEyLkRFRyRnZW5lSUQpLF0KYGBgCiAgCiAgV2UgYWxzbyBsb29rIGZvciBERSBnZW5lcyBvdmVyIGRheXMgMi03IGluIFJlY29yZC1zZXEgdXNpbmcgYSBsb29zZXIgY29uZmlkZW5jZSB0aHJlc2hvbGQgKHBhZGogPDAuMSkgdG8gaWRlbnRpZnkgY29uc2lzdGVudCBkaWV0LXNpZ25hdHVyZSBnZW5lcy4KICAgIApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpyZWMyLkRFRy5saXN0PC1saXN0KCkKcmVjMi5lcjwtbGlzdCgpCnJlYzIuZGU8LWxpc3QoKQpyZWMyLmdsb2JhbC5ERUc8LWMoKQpmb3IoaSBpbiB1bmlxdWUocmVjMmQkRGF5KSl7CiAgaWYoaTw4KXsKICAgIGR0PC1yZWMyZFt3aGljaChyZWMyZCREYXk9PWkpLCAxLCBkcm9wPUZBTFNFXQogICAgZHQkRGlldDwtZmFjdG9yKGR0JERpZXQpCiAgICBkZTwtcmVjMlssd2hpY2goY29sbmFtZXMocmVjMiklaW4lcm93bmFtZXMoZHQpKV0KICAgIHJlYzIuZGVbW2ldXTwtcmVjb1Jkc2VxLkRFKGRlLGR0LHRvb2w9J0RFU2VxMicpCiAgICByZWMyLmVyW1tpXV08LXJlY29SZHNlcS5ERShkZSxkdCx0b29sPSdlZGdlUicpCiAgICByZWMyLmRlLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzIuZGVbW2ldXSwgcCA9IDAuMSkKICAgIHJlYzIuZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMi5lcltbaV1dLCBwID0gMC4xKQogICAgcmVjMi5ERUcubGlzdFtbaV1dPC1yZWMyLmRlW1tpXV1baW50ZXJzZWN0KHJlYzIuZGUuZ2VuZXMsIHJlYzIuZXIuZ2VuZXMpLCBjKDEsNywgd2hpY2goZ3JlcGwoJ2xvZzJGb2xkQ2hhbmdlJywgY29sbmFtZXMocmVjMi5kZVtbaV1dKSkpKV0KICAgIHJlYzIuZ2xvYmFsLkRFRzwtIGMocmVjMi5nbG9iYWwuREVHLCBhcy5jaGFyYWN0ZXIoaW50ZXJzZWN0KHJlYzIuZGUuZ2VuZXMscmVjMi5lci5nZW5lcykpKQogIH0KfQpyZWMyLmdsb2JhbC5ERUcxPC1hcy5kYXRhLmZyYW1lKHRhYmxlKHJlYzIuZ2xvYmFsLkRFRylbb3JkZXIodGFibGUocmVjMi5nbG9iYWwuREVHKSwgZGVjcmVhc2luZyA9IFRSVUUpXSkKcmVjMi5nbG9iYWwuREVHMjwtYXMuZGF0YS5mcmFtZSh0YWJsZShyZWMyLmdsb2JhbC5ERUcpW29yZGVyKHRhYmxlKHJlYzIuZ2xvYmFsLkRFRyksIGRlY3JlYXNpbmcgPSBUUlVFKV0pCmNvbG5hbWVzKHJlYzIuZ2xvYmFsLkRFRzEpPC1jKCJnZW5lSUQiLCAiZGF5c19ERSIpCmNvbG5hbWVzKHJlYzIuZ2xvYmFsLkRFRzIpPC1jKCJnZW5lSUQiLCAiZGF5c19ERSIpCnJlYzIuZ2xvYmFsLkRFRzEkZ2VuZUlEPC1hcy5jaGFyYWN0ZXIocmVjMi5nbG9iYWwuREVHMSRnZW5lSUQpCnJlYzIuZ2xvYmFsLkRFRzIkZ2VuZUlEPC1hcy5jaGFyYWN0ZXIocmVjMi5nbG9iYWwuREVHMiRnZW5lSUQpCmZvcihpIGluIHVuaXF1ZShyZWMyZCREYXkpKXsKICBpZihpPDgpewogIHJlYzIuZ2xvYmFsLkRFRzEkVjE8LXJlYzIuZGVbW2ldXVtyZWMyLmdsb2JhbC5ERUcxJGdlbmVJRCw0XQogIGNvbG5hbWVzKHJlYzIuZ2xvYmFsLkRFRzEpW25jb2wocmVjMi5nbG9iYWwuREVHMSldPC1wYXN0ZTAoImxvZzJGb2xkQ2hhbmdlX0ZDX2RheSIsIGkpCiAgcmVjMi5nbG9iYWwuREVHMiRWMTwtcmVjMi5kZVtbaV1dW3JlYzIuZ2xvYmFsLkRFRzIkZ2VuZUlELDhdCiAgY29sbmFtZXMocmVjMi5nbG9iYWwuREVHMilbbmNvbChyZWMyLmdsb2JhbC5ERUcyKV08LXBhc3RlMCgibG9nMkZvbGRDaGFuZ2VfU0NfZGF5IiwgaSkKCiAgfQp9CnJlYzIuZ2xvYmFsLkRFRzEkbG9nMkZvbGRDaGFuZ2UubWF4PC1yZWMyLmdsb2JhbC5ERUcxWywzOm5jb2wocmVjMi5nbG9iYWwuREVHMSldW2NiaW5kKDE6bnJvdyhyZWMyLmdsb2JhbC5ERUcxWywzOm5jb2wocmVjMi5nbG9iYWwuREVHMSldKSwgbWF4LmNvbChyZXBsYWNlKHggPC0gYWJzKHJlYzIuZ2xvYmFsLkRFRzFbLDM6bmNvbChyZWMyLmdsb2JhbC5ERUcxKV0pLCBpcy5uYSh4KSwgLUluZikpKV0KcmVjMi5nbG9iYWwuREVHMiRsb2cyRm9sZENoYW5nZS5tYXg8LXJlYzIuZ2xvYmFsLkRFRzJbLDM6bmNvbChyZWMyLmdsb2JhbC5ERUcyKV1bY2JpbmQoMTpucm93KHJlYzIuZ2xvYmFsLkRFRzJbLDM6bmNvbChyZWMyLmdsb2JhbC5ERUcyKV0pLCBtYXguY29sKHJlcGxhY2UoeCA8LSBhYnMocmVjMi5nbG9iYWwuREVHMlssMzpuY29sKHJlYzIuZ2xvYmFsLkRFRzIpXSksIGlzLm5hKHgpLCAtSW5mKSkpXQoKcmVjMi5nbG9iYWwuREVHPC1yZWMyLmdsb2JhbC5ERUcxWyxjKDEsMixuY29sKHJlYzIuZ2xvYmFsLkRFRzEpKV0KcmVjMi5nbG9iYWwuREVHJFYxPC1yZWMyLmdsb2JhbC5ERUcyWyxuY29sKHJlYzIuZ2xvYmFsLkRFRzIpXQpjb2xuYW1lcyhyZWMyLmdsb2JhbC5ERUcpWzNdPC0ibG9nMkZvbGRDaGFuZ2UubWF4X0ZDIgpjb2xuYW1lcyhyZWMyLmdsb2JhbC5ERUcpWzRdPC0ibG9nMkZvbGRDaGFuZ2UubWF4X1NDIgoKYGBgCiAKIAojIyBQbG90dGluZyBpbmRpdmlkdWFsIERFR3M6CiAgV2UgcGxvdCB2c3QtdHJhbnNmb3JtZWQgZ2Vub21lLWFsaWduaW5nIHNwYWNlciBjb3VudHMgZm9yIDYgZ2VuZXMgaW4gdGhlIGdudFIgcGF0aHdheSBmb3IgY2hvdyBhbmQgc3RhcmNoIGZlZCBtaWNlIG9uIGRheSA3LgogIApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CmdudFJfZ2VuZXM8LWMoJ2VkYScsJ2VkZCcsICdnbnRUJywgJ2tkZ1QnLCAnZ250VScsICdnbnRLJykKZGU8LXJlYzJkX2RheTdbcmVjMmRfZGF5NyREaWV0IT0nRmF0JyxdCmR0PC1yZWMyX2RheTd0ZltnbnRSX2dlbmVzLCByb3duYW1lcyhkZSldCnJlYzIuZ250Ui5wbG90LmRmPC1kYXRhLmZyYW1lKERpZXQ9ZGUkRGlldCwgdChkdCkpCnJlYzIuZ250Ui5wbG90LmRmPC1tZWx0KHJlYzIuZ250Ui5wbG90LmRmLCBpZC52YXJzID0gJ0RpZXQnKQpyZWMyLmdudFIucGxvdDwtZ2dwbG90KHJlYzIuZ250Ui5wbG90LmRmLCBhZXMoeT12YWx1ZSwgeD12YXJpYWJsZSwgZmlsbD1EaWV0LCBjb2xvcj0nYmxhY2snKSkrZ2VvbV9ib3hwbG90KHNpemU9MC4yNCwgb3V0bGllci5zaXplPTApK2dlb21fcG9pbnQoc2l6ZT0wLjQ4LCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuNzUpKSt0aGVtZV9wdWIreWxhYigiZ2VuZS1hbGlnbmluZyBzcGFjZXIgY291bnRzICh2c3QtdHJhbnNmb3JtZWQpIikreGxhYigiZ2VuZSIpK2dndGl0bGUoIlJlY29yZC1zZXEgY291bnRzIG9uIGRheSA3IGZvciBnbnRSIGdlbmUiKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldFtjKDEsMyldKSkrc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImJsYWNrIiksIGd1aWRlPSdub25lJykKcmVjMi5nbnRSLnBsb3QrcGxvdF9hbm5vdGF0aW9uKCkKCmBgYAoKIyMgSGVhdG1hcCBmb3IgUmVjb3JkLXNlcSBhbmQgUk5BLXNlcSBERUdzIG9uIGRheSA3CiAgV2UgcGxvdCBoZWF0bWFwcyBzaG93aW5nIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIHNhbXBsZXMgdXNpbmcgZGV0ZWN0ZWQgREUgZ2VuZXMgZm9yIGJvdGggUmVjb3JkLXNlcSBhbmQgUk5BLXNlcSBvbiBkYXkgNy4gCiAgCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi4yLCBmaWcud2lkdGg9M30KY29sczwtIGNvbG9yUmFtcFBhbGV0dGUoYygiZG9kZ2VyYmx1ZTQiLCAid2hpdGUiLCJ2aW9sZXRyZWQ0IikpKDI1NikKZGhlYXRtYXA8LWFzLmRhdGEuZnJhbWUodChhcHBseShyZWMyX2RheTd0ZltyZWMyLkRFRyRnZW5lSUQsXSwgMSwgenNjb3Jlc3RhbmRhcmRpemUpKSkKaGVhdG1hcC5yZWMyLmRheTc8LXBoZWF0bWFwKGRoZWF0bWFwLCBhbm5vdGF0aW9uX2NvbCA9IHJlYzJkX2RheTdbLDEsIGRyb3A9RkFMU0VdLCBhbm5vdGF0aW9uX2NvbG9ycz1jb2xvdXJfY29kZSwgZm9udHNpemUgPSA1LCBmb250c2l6ZV9yb3cgPSA1LCBmb250c2l6ZV9jb2wgPSA1LCBjbHVzdGVyX3Jvd3MgPSBUUlVFLCB0cmVlaGVpZ2h0X3JvdyA9IDAsICB0cmVlaGVpZ2h0X2NvbCA9IDUsIHNob3dfY29sbmFtZXMgPSBGQUxTRSwgc2hvd19yb3duYW1lcyA9IEZBTFNFLCBjb2xvciA9IGNvbHMsZm9udHNpemVfbnVtYmVyPTUsIHdpZHRoPTIuMjgsIGhlaWdodD0yLjI4LCBtYWluPSdSZWNvcmQtc2VxIERFR3MgZGF5IDcnKQpoZWF0bWFwLmdlbmVsaXN0PC1oZWF0bWFwLnJlYzIuZGF5NyR0cmVlX3JvdyRsYWJlbHNbaGVhdG1hcC5yZWMyLmRheTckdHJlZV9yb3ckb3JkZXJdCmhlYXRtYXAuZ2VuZWxpc3Q8LXJlYzJfZGF5N3RmW2hlYXRtYXAuZ2VuZWxpc3QsXQpjb2xuYW1lcyhoZWF0bWFwLmdlbmVsaXN0KTwtYXMuY2hhcmFjdGVyKHJlYzJkX2RheTckRGlldCkKCmNvbHM8LSBjb2xvclJhbXBQYWxldHRlKGMoImRvZGdlcmJsdWU0IiwgIndoaXRlIiwidmlvbGV0cmVkNCIpKSgyNTYpCmRoZWF0bWFwPC1hcy5kYXRhLmZyYW1lKHQoYXBwbHkocm5hMl9kYXk3dGZbcm5hMi5ERUckZ2VuZUlELF0sIDEsIHpzY29yZXN0YW5kYXJkaXplKSkpCmhlYXRtYXAucm5hMi5kYXk3PC1waGVhdG1hcChkaGVhdG1hcCwgYW5ub3RhdGlvbl9jb2wgPSBybmEyZF9kYXk3WywxLCBkcm9wPUZBTFNFXSwgYW5ub3RhdGlvbl9jb2xvcnM9Y29sb3VyX2NvZGUsIGZvbnRzaXplID0gNSwgZm9udHNpemVfcm93ID0gNSwgZm9udHNpemVfY29sID0gNSwgY2x1c3Rlcl9yb3dzID0gVFJVRSwgdHJlZWhlaWdodF9yb3cgPSAwLCB0cmVlaGVpZ2h0X2NvbCA9IDUsIHNob3dfY29sbmFtZXMgPSBGQUxTRSxzaG93X3Jvd25hbWVzID0gRkFMU0UsIGNvbG9yID0gY29scyxmb250c2l6ZV9udW1iZXI9NSwgd2lkdGg9Mi4yOCwgaGVpZ2h0PTIuMjgsIG1haW49J1JOQS1zZXEgREVHcyBkYXkgNycpCmBgYAogIAojIyBWb2xjYW5vIHBsb3RzIGFuZCBoZWF0bWFwcyBmb3IgUmVjb3JkLXNlcSBhbmQgUk5BLXNlcSBERUdzOgpXZSBwZXJmb3JtIHBhaXJ3aXNlIERFIGFuYWx5c2lzIHVzaW5nIERFU2VxMiB0byBpZGVudGlmeSBsb2cyRkMgYW5kIHAtYWRqIHZhbHVlcyBmb3IgZWFjaCBkaWV0IHBhaXIgb24gZGF5IDcsIGFuZCBwbG90IHZvbGNhbm9lcyAobG9nMkZDPjEuNSwgcGFkajwwLjEpCiAgIAogIFJlY29yZC1zZXE6IApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CmxldmVsczwtc29ydCh1bmlxdWUocmVjMmRfZGF5N1ssMV0pKQpwYWlyd2lzZS5jb21ibzwtY29tYm4obGV2ZWxzLCAyKQpjb2xvci5jb21ibzwtY29tYm4oY29sb3VyX2NvZGUkRGlldCwgMikKcmVjMi5kZS52YWxzPC1saXN0KCkKcmVjMi5lZC52YWxzPC1saXN0KCkKdm9sLnBsb3RzPC1saXN0KCkKREVHPC1saXN0KCkKZm9yKGkgaW4gMTpkaW0ocGFpcndpc2UuY29tYm8pWzJdKXsKICBkczwtcmVjMmRfZGF5N1t3aGljaChyZWMyZF9kYXk3WywxXSVpbiVwYWlyd2lzZS5jb21ib1ssaV0pLF0KICBkcyREaWV0PC1hcy5jaGFyYWN0ZXIoZHMkRGlldCkKICBkdDwtcmVjMl9kYXk3Wyxyb3duYW1lcyhkcyldCiAgZHRmPC1yZWNvUmRzZXEudHJhbnNmb3JtKGR0LGRzKQogIHJlYzIuZGUudmFsc1tbaV1dIDwtIHJlY29SZHNlcS5ERShkdCwgZHMsIHRvb2wgPSAnREVTZXEyJykKICByZWMyLmVkLnZhbHNbW2ldXSA8LSByZWNvUmRzZXEuREUoZHQsIGRzLCB0b29sID0gJ2VkZ2VSJykKICByb3duYW1lcyhyZWMyLmRlLnZhbHNbW2ldXSkgPC0gcmVjMi5kZS52YWxzW1tpXV0kZ2VuZUlECiAgcm93bmFtZXMocmVjMi5lZC52YWxzW1tpXV0pIDwtIHJlYzIuZWQudmFsc1tbaV1dJGdlbmVJRAogIGRlc2VxLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzIuZGUudmFsc1tbaV1dLCBwID0gMC4xKQogIGVkZ2VyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzIuZWQudmFsc1tbaV1dLCBwID0gMC4xKQogIERFR1tbaV1dPC1kYXRhLmZyYW1lKHJvdy5uYW1lcz1pbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSxnZW5lSUQ9aW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksbG9nMkZvbGRjaGFuZ2U9cmVjMi5kZS52YWxzW1tpXV1baW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksIDRdLCBwYWRqPXJlYzIuZGUudmFsc1tbaV1dW2ludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLCA3XSkKICByaWJvc29tYWw8LWMoZ3JlcCgicnJzIiwgcm93bmFtZXMoREVHW1tpXV0pKSwgZ3JlcCgicnJsIiwgcm93bmFtZXMoREVHW1tpXV0pKSkKICBpZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICAgICAgREVHW1tpXV08LURFR1tbaV1dWy1yaWJvc29tYWwsXQogIH0KICBERUdbW2ldXSRnZW5lSUQ8LWFzLmNoYXJhY3RlcihERUdbW2ldXSRnZW5lSUQpCiAgcmVjMi5kZS52YWxzW1tpXV08LXJlYzIuZGUudmFsc1tbaV1dW2NvbXBsZXRlLmNhc2VzKHJlYzIuZGUudmFsc1tbaV1dKSxdCiAgcmVjMi5kZS52YWxzW1tpXV0kR3JvdXA8LSdOb25lJwogIHJlYzIuZGUudmFsc1tbaV1dJEdyb3VwWyB3aGljaChyZWMyLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZT4xLjUmcmVjMi5kZS52YWxzW1tpXV0kcGFkajwwLjEpXTwtcGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBhcy5jaGFyYWN0ZXIoc29ydCh1bmlxdWUoZHMkRGlldCkpWzJdKSkKICByZWMyLmRlLnZhbHNbW2ldXSRHcm91cFsgd2hpY2gocmVjMi5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U8KC0xLjUpJnJlYzIuZGUudmFsc1tbaV1dJHBhZGo8MC4xKV08LXBhc3RlMCgidXByZWd1bGF0ZWQuaW4uIiwgc29ydCh1bmlxdWUoZHMkRGlldCkpWzFdKQogcmVjMi5kZS52YWxzW1tpXV0kR3JvdXA8LWZhY3RvcihyZWMyLmRlLnZhbHNbW2ldXSRHcm91cCwgbGV2ZWxzID0gYyhwYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIGFzLmNoYXJhY3Rlcihzb3J0KHVuaXF1ZShkcyREaWV0KSlbMV0pKSwgcGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBzb3J0KHVuaXF1ZShkcyREaWV0KSlbMl0pLCAnTm9uZScgKSkKICByZWMyLmRlLnZhbHNbW2ldXSRsYWJlbDwtRkFMU0UKICBtMTwtcmVjMi5kZS52YWxzW1tpXV1bcmVjMi5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U+MS41LCAnZ2VuZUlEJ11bMToxMF0KICBtMjwtcmVjMi5kZS52YWxzW1tpXV1bcmVjMi5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U8KC0xLjUpLCAnZ2VuZUlEJ11bMToxMF0KICBtPC13aGljaChyZWMyLmRlLnZhbHNbW2ldXSRnZW5lSUQlaW4ldW5pb24obTEsbTIpKQogIGZvcihqIGluIG0pewogICAgaWYoYWJzKHJlYzIuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlW2pdKT4xLjUmcmVjMi5kZS52YWxzW1tpXV0kcGFkaltqXTwwLjEpewogICAgICByZWMyLmRlLnZhbHNbW2ldXSRsYWJlbFtqXTwtVFJVRQogICAgfQogIH0KICB2b2wucGxvdHNbW2ldXTwtZ2dwbG90KHJlYzIuZGUudmFsc1tbaV1dLCBhZXMoIHg9bG9nMkZvbGRDaGFuZ2UsIHk9KC1sb2cxMChwYWRqKSksIGNvbG9yPUdyb3VwKSkrc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKGNvbG9yLmNvbWJvWyxpXSwgJ2dyYXk3MCcpKStnZW9tX3BvaW50KHNpemU9MC4yNCkrZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSByZWMyLmRlLnZhbHNbW2ldXVt3aGljaChyZWMyLmRlLnZhbHNbW2ldXSRsYWJlbCksXSwgYWVzKCB4PWxvZzJGb2xkQ2hhbmdlLCB5PSgtbG9nMTAocGFkaikpLCBsYWJlbD1nZW5lSUQpLCBzaXplPTEuNzYsIHNob3cubGVnZW5kPUZBTFNFKSt0aGVtZV9wdWIrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMS41LCBzaXplPTAuMjQpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IC0xLjUsIHNpemU9MC4yNCkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgc2l6ZT0wLjI0KSt4bGFiKCJsb2cyIGZvbGQgY2hhbmdlIikreWxhYigiLWxvZzEwIHAtYWRqdXN0ZWQgdmFsdWUiKStndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTEuNSkpKQp9Cgp2b2wucGxvdHNbWzFdXSArIHZvbC5wbG90c1tbMl1dICt2b2wucGxvdHNbWzNdXSArIHBsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKStwbG90X2xheW91dChuY29sID0gMikKCmBgYAogIAogIFJOQS1zZXE6CmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi4yLCBmaWcud2lkdGg9M30KbGV2ZWxzPC1zb3J0KHVuaXF1ZShybmEyZF9kYXk3WywxXSkpCnBhaXJ3aXNlLmNvbWJvPC1jb21ibihsZXZlbHMsIDIpCmNvbG9yLmNvbWJvPC1jb21ibihjb2xvdXJfY29kZSREaWV0LCAyKQpybmEyLmRlLnZhbHM8LWxpc3QoKQpybmEyLmVkLnZhbHM8LWxpc3QoKQp2b2wucGxvdHM8LWxpc3QoKQpERUc8LWxpc3QoKQpmb3IoaSBpbiAxOmRpbShwYWlyd2lzZS5jb21ibylbMl0pewogIGRzPC1ybmEyZF9kYXk3W3doaWNoKHJuYTJkX2RheTdbLDFdJWluJXBhaXJ3aXNlLmNvbWJvWyxpXSksXQogIGRzJERpZXQ8LWFzLmNoYXJhY3RlcihkcyREaWV0KQogIGR0PC1ybmEyX2RheTdbLHJvd25hbWVzKGRzKV0KICBkdGY8LXJlY29SZHNlcS50cmFuc2Zvcm0oZHQsZHMpCiAgcm5hMi5kZS52YWxzW1tpXV0gPC0gcmVjb1Jkc2VxLkRFKGR0LCBkcywgdG9vbCA9ICdERVNlcTInKQogIHJuYTIuZWQudmFsc1tbaV1dIDwtIHJlY29SZHNlcS5ERShkdCwgZHMsIHRvb2wgPSAnZWRnZVInKQogIHJvd25hbWVzKHJuYTIuZGUudmFsc1tbaV1dKSA8LSBybmEyLmRlLnZhbHNbW2ldXSRnZW5lSUQKICByb3duYW1lcyhybmEyLmVkLnZhbHNbW2ldXSkgPC0gcm5hMi5lZC52YWxzW1tpXV0kZ2VuZUlECiAgZGVzZXEuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocm5hMi5kZS52YWxzW1tpXV0sIHAgPSAwLjEpCiAgZWRnZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocm5hMi5lZC52YWxzW1tpXV0sIHAgPSAwLjEpCiAgREVHW1tpXV08LWRhdGEuZnJhbWUocm93Lm5hbWVzPWludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLGdlbmVJRD1pbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSxsb2cyRm9sZGNoYW5nZT1ybmEyLmRlLnZhbHNbW2ldXVtpbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSwgNF0sIHBhZGo9cm5hMi5kZS52YWxzW1tpXV1baW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksIDddKQogIHJpYm9zb21hbDwtYyhncmVwKCJycnMiLCByb3duYW1lcyhERUdbW2ldXSkpLCBncmVwKCJycmwiLCByb3duYW1lcyhERUdbW2ldXSkpKQogIGlmKGxlbmd0aChyaWJvc29tYWwpPjApewogICAgICBERUdbW2ldXTwtREVHW1tpXV1bLXJpYm9zb21hbCxdCiAgfQogIERFR1tbaV1dJGdlbmVJRDwtYXMuY2hhcmFjdGVyKERFR1tbaV1dJGdlbmVJRCkKICBybmEyLmRlLnZhbHNbW2ldXTwtcm5hMi5kZS52YWxzW1tpXV1bY29tcGxldGUuY2FzZXMocm5hMi5kZS52YWxzW1tpXV0pLF0KICBybmEyLmRlLnZhbHNbW2ldXSRHcm91cDwtJ05vbmUnCiAgcm5hMi5kZS52YWxzW1tpXV0kR3JvdXBbIHdoaWNoKHJuYTIuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlPjEuNSZybmEyLmRlLnZhbHNbW2ldXSRwYWRqPDAuMSldPC1wYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIGFzLmNoYXJhY3Rlcihzb3J0KHVuaXF1ZShkcyREaWV0KSlbMl0pKQogIHJuYTIuZGUudmFsc1tbaV1dJEdyb3VwWyB3aGljaChybmEyLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZTwoLTEuNSkmcm5hMi5kZS52YWxzW1tpXV0kcGFkajwwLjEpXTwtcGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBzb3J0KHVuaXF1ZShkcyREaWV0KSlbMV0pCiBybmEyLmRlLnZhbHNbW2ldXSRHcm91cDwtZmFjdG9yKHJuYTIuZGUudmFsc1tbaV1dJEdyb3VwLCBsZXZlbHMgPSBjKHBhc3RlMCgidXByZWd1bGF0ZWQuaW4uIiwgYXMuY2hhcmFjdGVyKHNvcnQodW5pcXVlKGRzJERpZXQpKVsxXSkpLCBwYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIHNvcnQodW5pcXVlKGRzJERpZXQpKVsyXSksICdOb25lJyApKQogIHJuYTIuZGUudmFsc1tbaV1dJGxhYmVsPC1GQUxTRQogIG0xPC1ybmEyLmRlLnZhbHNbW2ldXVtybmEyLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZT4xLjUsICdnZW5lSUQnXVsxOjEwXQogIG0yPC1ybmEyLmRlLnZhbHNbW2ldXVtybmEyLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZTwoLTEuNSksICdnZW5lSUQnXVsxOjEwXQogIG08LXdoaWNoKHJuYTIuZGUudmFsc1tbaV1dJGdlbmVJRCVpbiV1bmlvbihtMSxtMikpCiAgZm9yKGogaW4gbSl7CiAgICBpZihhYnMocm5hMi5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2Vbal0pPjEuNSZybmEyLmRlLnZhbHNbW2ldXSRwYWRqW2pdPDAuMSl7CiAgICAgIHJuYTIuZGUudmFsc1tbaV1dJGxhYmVsW2pdPC1UUlVFCiAgICB9CiAgfQogIHZvbC5wbG90c1tbaV1dPC1nZ3Bsb3Qocm5hMi5kZS52YWxzW1tpXV0sIGFlcyggeD1sb2cyRm9sZENoYW5nZSwgeT0oLWxvZzEwKHBhZGopKSwgY29sb3I9R3JvdXApKStzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoY29sb3IuY29tYm9bLGldLCAnZ3JheTcwJykpK2dlb21fcG9pbnQoc2l6ZT0wLjI0KStnZW9tX3RleHRfcmVwZWwoZGF0YSA9IHJuYTIuZGUudmFsc1tbaV1dW3doaWNoKHJuYTIuZGUudmFsc1tbaV1dJGxhYmVsKSxdLCBhZXMoIHg9bG9nMkZvbGRDaGFuZ2UsIHk9KC1sb2cxMChwYWRqKSksIGxhYmVsPWdlbmVJRCksIHNpemU9MS43Niwgc2hvdy5sZWdlbmQ9RkFMU0UpK3RoZW1lX3B1YitnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAxLjUsIHNpemU9MC4yNCkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gLTEuNSwgc2l6ZT0wLjI0KStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLCBzaXplPTAuMjQpK3hsYWIoImxvZzIgZm9sZCBjaGFuZ2UiKSt5bGFiKCItbG9nMTAgcC1hZGp1c3RlZCB2YWx1ZSIpK2d1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MS41KSkpCn0KCnZvbC5wbG90c1tbMV1dICsgdm9sLnBsb3RzW1syXV0gK3ZvbC5wbG90c1tbM11dICsgcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSAnQScpK3Bsb3RfbGF5b3V0KG5jb2wgPSAyKQoKCgpgYGAKCgojIyBIaWVyYXJjaGljYWwgY2x1c3RlcmluZyBvbiBmaW5hbCBkYXkgdXNpbmcgREVHcwoKICBXZSB3YW50IHRvIGNoZWNrIHdoZXRoZXIgaW5mb3JtYXRpb24gYWJvdXQgZGlldCBncm91cHMgcHJpb3IgdG8gc3dpdGNoIGNhbiBiZSByZXRyaWV2ZWQgYXQgZGF5IDIwIC0gaS5lIDEzIGRheXMgYWZ0ZXIgdGhlIHN3aXRjaC4gRm9yIHRoaXMsIHdlIHVzZSBkaWV0IHNpZ25hdHVyZSBnZW5lcyBpZGVudGlmaWVkIGJlZm9yZSB0aGUgc3dpdGNoIChERUdzKSB0byBoaWVyYXJjaGljYWxseSBjbHVzdGVyIHRoZSBncm91cHMuIFdlIGNhbiBhbG1vc3QgcGVyZmVjdGx5IGNsYXNzaWZ5IGdyb3VwcyB1c2luZyBSZWNvcmQtc2VxIGRhdGEsIHdoaWxlIGZvciBSTkEtc2VxLCB0aGUgZ3JvdXBzIGNvbnZlcmdlLiAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi4yLCBmaWcud2lkdGg9M30KcmVjMmRfZGF5MjA8LXJlYzJkW3JlYzJkJERheT09MjAsXQpyZWMyX2RheTIwPC1yZWMyWyxyb3duYW1lcyhyZWMyZF9kYXkyMCldCnJlYzJfZGF5MjBfdGY8LXJlY29SZHNlcS50cmFuc2Zvcm0ocmVjMl9kYXkyMCwgcmVjMmRfZGF5MjApCgpjb2xzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJkb2RnZXJibHVlNCIsICJ3aGl0ZSIsInZpb2xldHJlZDQiKSkoMjU2KQpyaWJvc29tYWw8LWMoZ3JlcCgicnJzIiwgcmVjMi5nbG9iYWwuREVHJGdlbmVJRCksIGdyZXAoInJybCIsIHJlYzIuZ2xvYmFsLkRFRyRnZW5lSUQpKQppZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICByZWMyLmdsb2JhbC5ERUc8LXJlYzIuZ2xvYmFsLkRFR1stcmlib3NvbWFsLF0KfQpyZWMyLmdlbmVTaG9ydExpc3Q8LXVuaXF1ZShjKHJlYzIuZ2xvYmFsLkRFR1t3aGljaChyZWMyLmdsb2JhbC5ERUckbG9nMkZvbGRDaGFuZ2UubWF4X0ZDPjIuNSksIDFdWzE6MTBdLCByZWMyLmdsb2JhbC5ERUdbd2hpY2gocmVjMi5nbG9iYWwuREVHJGxvZzJGb2xkQ2hhbmdlLm1heF9TQz4yLjUpLCAxXVsxOjEwXSxyZWMyLmdsb2JhbC5ERUdbd2hpY2gocmVjMi5nbG9iYWwuREVHJGxvZzJGb2xkQ2hhbmdlLm1heF9TQzwoLTIuNSkmcmVjMi5nbG9iYWwuREVHJGxvZzJGb2xkQ2hhbmdlLm1heF9GQzwoLTIuNSkpLCAxXVsxOjEwXSkpCmRoZWF0bWFwPC1hcy5kYXRhLmZyYW1lKHQoYXBwbHkocmVjMl9kYXkyMF90ZltyZWMyLmdlbmVTaG9ydExpc3QsXSwgMSwgenNjb3Jlc3RhbmRhcmRpemUpKSkKaGVhdG1hcC5yZWMyPC1waGVhdG1hcChkaGVhdG1hcCwgYW5ub3RhdGlvbl9jb2wgPSByZWMyZF9kYXkyMFssMSwgZHJvcD1GQUxTRV0sIGFubm90YXRpb25fY29sb3JzPWNvbG91cl9jb2RlLCB0cmVlaGVpZ2h0X3Jvdz0wLCBmb250c2l6ZSA9IDUsIGZvbnRzaXplX3JvdyA9IDUsIGZvbnRzaXplX2NvbCA9IDUsIGNsdXN0ZXJfcm93cyA9IEZBTFNFLCBzaG93X2NvbG5hbWVzID0gRkFMU0UsY29sb3IgPSBjb2xzLGZvbnRzaXplX251bWJlcj01LCBtYWluPSdIaWVyYXJjaGljYWwgY2x1c3RlcmluZyBvZiBSZWNvcmQtc2VxIGRhdGEgb24gZGF5IDIwIGJhc2VkIG9uIGRpZXQgc2lnbmF0dXJlIGdlbmVzJykKaGVhdG1hcC5nZW5lbGlzdDwtcmVjMl9kYXkyMF90ZltyZWMyLmdlbmVTaG9ydExpc3QsXQpjb2xuYW1lcyhoZWF0bWFwLmdlbmVsaXN0KTwtYXMuY2hhcmFjdGVyKHJlYzJkX2RheTIwJERpZXQpCgpybmEyZF9kYXkyMDwtcm5hMmRbcm5hMmQkRGF5PT0yMCxdCnJuYTJfZGF5MjA8LXJuYTJbLHJvd25hbWVzKHJuYTJkX2RheTIwKV0Kcm5hMl9kYXkyMF90ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShybmEyX2RheTIwLCBybmEyZF9kYXkyMCkKcm5hMi5nZW5lU2hvcnRMaXN0PC11bmlxdWUoYyhybmEyLkRFR1tybmEyLkRFRyRsb2cyRm9sZENoYW5nZS5GYXRfdnNfQ2hvdz4yLjUsIDFdWzE6MTBdLCBybmEyLkRFR1tybmEyLkRFRyRsb2cyRm9sZENoYW5nZS5TdGFyY2hfdnNfQ2hvdz4yLjUsIDFdWzE6MTBdLCBybmEyLkRFR1t3aGljaChyb3dNZWFucyhybmEyLkRFR1ssMzo0XSk8KC0yLjUpKSwgMV1bMToxMF0pKQpjb2xzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJkb2RnZXJibHVlNCIsICJ3aGl0ZSIsInZpb2xldHJlZDQiKSkoMjU2KQpkaGVhdG1hcDwtYXMuZGF0YS5mcmFtZSh0KGFwcGx5KHJuYTJfZGF5MjBfdGZbcm5hMi5nZW5lU2hvcnRMaXN0LF0sIDEsIHpzY29yZXN0YW5kYXJkaXplKSkpCmhlYXRtYXAucm5hMjwtcGhlYXRtYXAoZGhlYXRtYXAsIGFubm90YXRpb25fY29sID0gcm5hMmRfZGF5MjBbLDEsIGRyb3A9RkFMU0VdLCBhbm5vdGF0aW9uX2NvbG9ycz1jb2xvdXJfY29kZSwgdHJlZWhlaWdodF9yb3c9MCwgZm9udHNpemUgPSA1LCBmb250c2l6ZV9yb3cgPSA1LCBmb250c2l6ZV9jb2wgPSA1LCBjbHVzdGVyX3Jvd3MgPSBGQUxTRSwgc2hvd19jb2xuYW1lcyA9IEZBTFNFLCBjb2xvciA9IGNvbHMsZm9udHNpemVfbnVtYmVyPTUsIG1haW49J0hpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIFJOQS1zZXEgZGF0YSBvbiBkYXkgMjAgYmFzZWQgb24gZGlldCBzaWduYXR1cmUgZ2VuZXMnKQpoZWF0bWFwLmdlbmVsaXN0PC1ybmEyX2RheTIwX3RmW3JuYTIuZ2VuZVNob3J0TGlzdCxdCmNvbG5hbWVzKGhlYXRtYXAuZ2VuZWxpc3QpPC1hcy5jaGFyYWN0ZXIocm5hMmRfZGF5MjAkRGlldCkKYGBgCgojIyBFY29jeWMgYW5hbHlzaXM6CgogIFdlIGNyZWF0ZSBlbnJpY2htZW50IHBsb3RzIGZvciB0b3AgZGlmZmVyZW50aWFsbHkgcmVndWxhdGVkIHBhdGh3YXlzIGlkZW50aWZpZWQgYnkgRWNvY3ljIHVzaW5nIHRoZSBwYWlyd2lzZSBERUcgbGlzdHMgZ2VuZXJhdGVkIGhlcmUuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CkNob3dYU3RhcmNoLnBhdGh3YXk8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS9DaG93LlN0YXJjaC5wYXRod2F5LnR4dCIsIGhlYWRlcj1UUlVFLCBzZXAgPSAnXHQnKSkKQ2hvd1hTdGFyY2gucGF0aHdheSRwLnZhbHVlc1tDaG93WFN0YXJjaC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uQ2hvdyddPC1sb2cxMChDaG93WFN0YXJjaC5wYXRod2F5JHAudmFsdWVzW0Nob3dYU3RhcmNoLnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5DaG93J10pKigtMSkKQ2hvd1hTdGFyY2gucGF0aHdheSRwLnZhbHVlc1tDaG93WFN0YXJjaC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uU3RhcmNoJ108LWxvZzEwKENob3dYU3RhcmNoLnBhdGh3YXkkcC52YWx1ZXNbQ2hvd1hTdGFyY2gucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLlN0YXJjaCddKQpDaG93WFN0YXJjaC5wYXRod2F5PC1DaG93WFN0YXJjaC5wYXRod2F5W29yZGVyKENob3dYU3RhcmNoLnBhdGh3YXkkcC52YWx1ZXMpLF0KQ2hvd1hTdGFyY2gucGF0aHdheS5wbG90PC1nZ3Bsb3QoQ2hvd1hTdGFyY2gucGF0aHdheSwgYWVzKHg9UGF0aHdheSwgeT1wLnZhbHVlcywgc2l6ZT1udW1iZXIub2YuZ2VuZXMsIGNvbG91cj1ncm91cCApKStnZW9tX3BvaW50KCkrY29vcmRfZmxpcCgpK3RoZW1lX3B1YitzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDEsMykpK2dlb21faGxpbmUoeWludGVyY2VwdCA9ICgtMS4zMDEwMyksIHNpemU9MC4yNCkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLjMwMTAzLCBzaXplPTAuMjQpK3hsYWIoIiIpK3lsYWIoIi1sb2cxMCBwLWFkanVzdGVkIHZhbHVlIikrIGxhYnMoc2l6ZSA9ICJHZW5lcyBkZXRlY3RlZCIpK3NjYWxlX3hfZGlzY3JldGUobGltaXRzPUNob3dYU3RhcmNoLnBhdGh3YXkkUGF0aHdheSkrc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoYXMuY2hhcmFjdGVyKGNvbG91cl9jb2RlJERpZXRbYygxLDMpXSkpKQoKQ2hvd1hTdGFyY2gucGF0aHdheS5wbG90K3Bsb3RfYW5ub3RhdGlvbigpCgpDaG93WEZhdC5wYXRod2F5PC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvQ2hvdy5GYXQucGF0aHdheS50eHQiLCBoZWFkZXI9VFJVRSwgc2VwID0gJ1x0JykpCkNob3dYRmF0LnBhdGh3YXkkcC52YWx1ZXNbQ2hvd1hGYXQucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLkNob3cnXTwtbG9nMTAoQ2hvd1hGYXQucGF0aHdheSRwLnZhbHVlc1tDaG93WEZhdC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uQ2hvdyddKSooLTEpCkNob3dYRmF0LnBhdGh3YXkkcC52YWx1ZXNbQ2hvd1hGYXQucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLkZhdCddPC1sb2cxMChDaG93WEZhdC5wYXRod2F5JHAudmFsdWVzW0Nob3dYRmF0LnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5GYXQnXSkKQ2hvd1hGYXQucGF0aHdheTwtQ2hvd1hGYXQucGF0aHdheVtvcmRlcihDaG93WEZhdC5wYXRod2F5JHAudmFsdWVzKSxdCkNob3dYRmF0LnBhdGh3YXkucGxvdDwtZ2dwbG90KENob3dYRmF0LnBhdGh3YXksIGFlcyh4PVBhdGh3YXksIHk9cC52YWx1ZXMsIHNpemU9bnVtYmVyLm9mLmdlbmVzLCBjb2xvdXI9Z3JvdXAgKSkrZ2VvbV9wb2ludCgpK2Nvb3JkX2ZsaXAoKSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygxLDMpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAoLTEuMzAxMDMpLCBzaXplPTAuMjQpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMS4zMDEwMywgc2l6ZT0wLjI0KSt4bGFiKCIiKSt5bGFiKCItbG9nMTAgcC1hZGp1c3RlZCB2YWx1ZSIpKyBsYWJzKHNpemUgPSAiR2VuZXMgZGV0ZWN0ZWQiKStzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cz1DaG93WEZhdC5wYXRod2F5JFBhdGh3YXkpK3NjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKGFzLmNoYXJhY3Rlcihjb2xvdXJfY29kZSREaWV0W2MoMSwyKV0pKSkKCkNob3dYRmF0LnBhdGh3YXkucGxvdCtwbG90X2Fubm90YXRpb24oKQoKRmF0WFN0YXJjaC5wYXRod2F5PC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvRmF0LlN0YXJjaC5wYXRod2F5LnR4dCIsIGhlYWRlcj1UUlVFLCBzZXAgPSAnXHQnKSkKRmF0WFN0YXJjaC5wYXRod2F5JHAudmFsdWVzW0ZhdFhTdGFyY2gucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLkZhdCddPC1sb2cxMChGYXRYU3RhcmNoLnBhdGh3YXkkcC52YWx1ZXNbRmF0WFN0YXJjaC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uRmF0J10pKigtMSkKRmF0WFN0YXJjaC5wYXRod2F5JHAudmFsdWVzW0ZhdFhTdGFyY2gucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLlN0YXJjaCddPC1sb2cxMChGYXRYU3RhcmNoLnBhdGh3YXkkcC52YWx1ZXNbRmF0WFN0YXJjaC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uU3RhcmNoJ10pCkZhdFhTdGFyY2gucGF0aHdheTwtRmF0WFN0YXJjaC5wYXRod2F5W29yZGVyKEZhdFhTdGFyY2gucGF0aHdheSRwLnZhbHVlcyksXQpGYXRYU3RhcmNoLnBhdGh3YXkucGxvdDwtZ2dwbG90KEZhdFhTdGFyY2gucGF0aHdheSwgYWVzKHg9UGF0aHdheSwgeT1wLnZhbHVlcywgc2l6ZT1udW1iZXIub2YuZ2VuZXMsIGNvbG91cj1ncm91cCApKStnZW9tX3BvaW50KCkrY29vcmRfZmxpcCgpK3RoZW1lX3B1YitzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDEsMykpK2dlb21faGxpbmUoeWludGVyY2VwdCA9ICgtMS4zMDEwMyksIHNpemU9MC4yNCkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLjMwMTAzLCBzaXplPTAuMjQpK3hsYWIoIiIpK3lsYWIoIi1sb2cxMCBwLWFkanVzdGVkIHZhbHVlIikrIGxhYnMoc2l6ZSA9ICJHZW5lcyBkZXRlY3RlZCIpK3NjYWxlX3hfZGlzY3JldGUobGltaXRzPUZhdFhTdGFyY2gucGF0aHdheSRQYXRod2F5KStzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhhcy5jaGFyYWN0ZXIoY29sb3VyX2NvZGUkRGlldFtjKDIsMyldKSkpCgpGYXRYU3RhcmNoLnBhdGh3YXkucGxvdCtwbG90X2Fubm90YXRpb24oKQoKCmBgYAojIENoZWNraW5nIHJlcHJvZHVjaWJpbGl0eSBvZiBjbGFzc2lmaWVyIGdlbmVzIChERUdzKQoKIFdlIHdhbnQgdG8gY29uZmlybSB0aGF0IHRoZXJlIGlzIGFuIG92ZXJsYXAgYW1vbmcgdGhlIERFR3MgIGlkZW50aWZpZWQgaW4gdGhlIHR3byBleHBlcmltZW50YWwgcmVwbGljYXRlcywgYW5kIHRoZSBkaXJlY3Rpb24gb2YgZGlmZmVyZW50aWFsIHJlZ3VsYXRpb24gaXMgY29uc2lzdGVudC4gV2UgdXNlIHRoZSBnZW5lcyB0aGF0IGFyZSB1cHJlZ3VsYXRlZC9kb3ducmVndWxhdGVkIGluIHRoZSBDaG93IGdyb3VwIGNvbXBhcmVkIHRvIHRoZSBTdGFyY2ggZ3JvdXAgb24gZGF5IDcuCiAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodCA9IDMsIGZpZy53aWR0aCA9IDMsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQpkZXNlcS5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMxLmRlLnZhbHNbWzJdXSwgcCA9IDAuMSkKZWRnZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMS5lZC52YWxzW1syXV0sIHAgPSAwLjEpCnJlYzEuQ2hvdy52LlN0YXJjaC5ERUc8LWRhdGEuZnJhbWUocm93Lm5hbWVzID0gaW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksZ2VuZUlEPWludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLCBsb2cyRkM9cmVjMS5kZS52YWxzW1syXV1baW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksNF0pCgpkZXNlcS5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMyLmRlLnZhbHNbWzJdXSwgcCA9IDAuMSkKZWRnZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMi5lZC52YWxzW1syXV0sIHAgPSAwLjEpCnJlYzIuQ2hvdy52LlN0YXJjaC5ERUc8LWRhdGEuZnJhbWUocm93Lm5hbWVzID0gaW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksZ2VuZUlEPWludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLCBsb2cyRkM9cmVjMi5kZS52YWxzW1syXV1baW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksNF0pCgpwbG90KGV1bGVyKGxpc3QocmVjMSA9IHJlYzEuQ2hvdy52LlN0YXJjaC5ERUckZ2VuZUlELCByZWMyID0gcmVjMi5DaG93LnYuU3RhcmNoLkRFRyRnZW5lSUQpKSAsIHF1YW50aXRpZXM9VFJVRSkKCmBgYAogIEZpbmFsbHksIHdlIHBsb3QgYSBjb3JyZWxhdGlvbiBwbG90IGJhc2VkIG9uIHRoZSBsb2cyRkMgZGV0ZWN0ZWQgZm9yIERFR3MgZm9yIHRoZSB0d28gZXhwZXJpbWVudHMsIGFuZCBlc3RpbWF0ZSB0aGUgbnVtYmVyIG9mIERFR3MgcmVndWxhdGVkIGluIGEgc2ltaWxhciBkaXJlY3Rpb24uCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CiBERUcuY29tcGFyZTwtZGF0YS5mcmFtZShnZW5lSUQ9aW50ZXJzZWN0KHJlYzEuQ2hvdy52LlN0YXJjaC5ERUckZ2VuZUlELCByZWMyLkNob3cudi5TdGFyY2guREVHJGdlbmVJRCkpCkRFRy5jb21wYXJlJGdlbmVJRDwtYXMuY2hhcmFjdGVyKERFRy5jb21wYXJlJGdlbmVJRCkKREVHLmNvbXBhcmUkcmVjMV9sb2cyRkM8LXJlYzEuQ2hvdy52LlN0YXJjaC5ERUdbREVHLmNvbXBhcmUkZ2VuZUlELDJdCkRFRy5jb21wYXJlJHJlYzJfbG9nMkZDPC1yZWMyLkNob3cudi5TdGFyY2guREVHW0RFRy5jb21wYXJlJGdlbmVJRCwyXQpERUcuY29tcGFyZTwtREVHLmNvbXBhcmVbY29tcGxldGUuY2FzZXMoREVHLmNvbXBhcmUpLCBdCnIyPC1yb3VuZChjb3IoREVHLmNvbXBhcmUkcmVjMV9sb2cyRkMsIERFRy5jb21wYXJlJHJlYzJfbG9nMkZDKV4yLDIpCm48LXJvdW5kKGxlbmd0aCh3aGljaChERUcuY29tcGFyZSRyZWMxX2xvZzJGQypERUcuY29tcGFyZSRyZWMyX2xvZzJGQz4wKSkqMTAwL2xlbmd0aChERUcuY29tcGFyZSRnZW5lSUQpKQpERUcuc2NhdHRlcnBsb3Q8LWdncGxvdChERUcuY29tcGFyZSwgYWVzKHk9cmVjMV9sb2cyRkMsIHg9cmVjMl9sb2cyRkMpKStnZW9tX3BvaW50KHNpemU9MC40OCwgYWVzKGNvbG91cj0nZ3JheTEwJykpK2dlb21fc21vb3RoKG1ldGhvZCA9ICdsbScsIHNlID0gRkFMU0UsIHNpemU9MC40OCkrdGhlbWVfcHViK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KSt4bGFiKCJsb2cyIGZvbGQgY2hhbmdlIG9mIERFR3MgZGV0ZWN0ZWQgaW4gdHJhbnNpZW50IGRpZXQgMiIpK3lsYWIoImNvcnJlc3BvbmRpbmcgbG9nMiBmb2xkIGNoYW5nZSBpbiB0cmFuc2llbnQgZGlldCAxIikrYW5ub3RhdGUoInRleHQiLCAgeD1JbmYsIHkgPSBJbmYsIGxhYmVsID0gcGFzdGUwKCJSXjIgPSIsYXMuY2hhcmFjdGVyKHIyKSwgIiBcbiBERUdzIHJlZ3VsYXRlZCBpbiB0aGUgc2FtZSBkaXJlY3Rpb24gPSAiLGFzLmNoYXJhY3RlcihuKSwgIiUiKSwgdmp1c3Q9MSwgaGp1c3Q9MSwgc2l6ZT0zKStzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygnZ3JheTEwJyksIGd1aWRlPSdub25lJykrZ2d0aXRsZSgiQ29ycmVsYXRpb24gb2Ygb3ZlcmxhcHBpbmcgREVHcyBkZXRlY3RlZCBpbiBib3RoIGV4cGVyaW1lbnRzIikKREVHLnNjYXR0ZXJwbG90K3Bsb3RfYW5ub3RhdGlvbigpCmBgYAojIEluZm9ybWF0aW9uIGFib3V0IFIgc2Vzc2lvbgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzZXNzaW9uSW5mbygpCmBgYA==